From ab9b3f567569fadf0eb1b3de0550c8cbb72f4f82 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:30:34 +0100 Subject: [PATCH 01/21] add pdf opening ability --- .../logic/importer/util/FileFieldParser.java | 26 +++-- .../BibtexTextDocumentService.java | 8 +- .../languageserver/LspClientHandler.java | 2 +- .../util/LspIntegrityCheck.java | 24 +++-- .../languageserver/util/LspLinkHandler.java | 15 +-- .../definition/BibDefinitionProvider.java | 96 +++++++++++++++++++ .../util/definition/DefinitionProvider.java | 4 +- .../definition/DefinitionProviderFactory.java | 5 +- .../definition/LatexDefinitionProvider.java | 8 +- 9 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java diff --git a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index 983b0db8a58..931e6b8e1ac 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -4,10 +4,13 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jabref.logic.util.URLUtil; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.util.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +24,7 @@ public class FileFieldParser { private boolean windowsPath; - public FileFieldParser(String value) { + private FileFieldParser(String value) { if (value == null) { this.value = null; } else { @@ -46,11 +49,16 @@ public FileFieldParser(String value) { public static List parse(String value) { // We need state to have a more clean code. Thus, we instantiate the class and then return the result FileFieldParser fileFieldParser = new FileFieldParser(value); - return fileFieldParser.parse(); + return fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).toList(); } - public List parse() { - List files = new ArrayList<>(); + public static Map parseToPosition(String value) { + FileFieldParser fileFieldParser = new FileFieldParser(value); + return fileFieldParser.parse().stream().collect(HashMap::new, (map, position) -> map.put(position.linkedFile(), position.range()), HashMap::putAll); + } + + private List parse() { + List files = new ArrayList<>(); if ((value == null) || value.trim().isEmpty()) { return files; @@ -59,7 +67,7 @@ public List parse() { if (LinkedFile.isOnlineLink(value.trim())) { // needs to be modifiable try { - return List.of(new LinkedFile(URLUtil.create(value), "")); + return List.of(new LinkedFilePosition(new LinkedFile(URLUtil.create(value), ""), new Range(0, value.length() - 1))); } catch (MalformedURLException e) { LOGGER.error("invalid url", e); return files; @@ -72,6 +80,7 @@ public List parse() { resetDataStructuresForNextElement(); boolean inXmlChar = false; boolean escaped = false; + int startColumn = 0; for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); @@ -114,7 +123,8 @@ public List parse() { } } else if (!escaped && (c == ';') && !inXmlChar) { linkedFileData.add(charactersOfCurrentElement.toString()); - files.add(convert(linkedFileData)); + files.add(new LinkedFilePosition(convert(linkedFileData), new Range(startColumn, i))); + startColumn = i + 1; // next iteration resetDataStructuresForNextElement(); @@ -127,7 +137,7 @@ public List parse() { linkedFileData.add(charactersOfCurrentElement.toString()); } if (!linkedFileData.isEmpty()) { - files.add(convert(linkedFileData)); + files.add(new LinkedFilePosition(convert(linkedFileData), new Range(startColumn, value.length() - 1))); } return files; } @@ -193,4 +203,6 @@ static LinkedFile convert(List entry) { entry.clear(); return field; } + + private record LinkedFilePosition(LinkedFile linkedFile, Range range) { } } diff --git a/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java b/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java index 53962a3dcbc..4e883dd158f 100644 --- a/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java +++ b/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java @@ -65,9 +65,8 @@ public void didOpen(DidOpenTextDocumentParams params) { if ("bibtex".equals(textDocument.getLanguageId())) { diagnosticHandler.computeAndPublishDiagnostics(client, textDocument.getUri(), textDocument.getText(), textDocument.getVersion()); - } else { - contentCache.put(textDocument.getUri(), textDocument.getText()); } + contentCache.put(textDocument.getUri(), textDocument.getText()); } @Override @@ -79,9 +78,8 @@ public void didChange(DidChangeTextDocumentParams params) { if ("bibtex".equalsIgnoreCase(languageId)) { diagnosticHandler.computeAndPublishDiagnostics(client, textDocument.getUri(), contentChange.getText(), textDocument.getVersion()); - } else { - contentCache.put(textDocument.getUri(), contentChange.getText()); } + contentCache.put(textDocument.getUri(), contentChange.getText()); } @Override @@ -112,7 +110,7 @@ public CompletableFuture> documentLink(DocumentLinkParams par return CompletableFuture.completedFuture(List.of()); } String fileUri = params.getTextDocument().getUri(); - return linkHandler.provideDocumentLinks(fileUriToLanguageId.get(fileUri), contentCache.get(fileUri)); + return linkHandler.provideDocumentLinks(fileUri, fileUriToLanguageId.get(fileUri), contentCache.get(fileUri)); } @Override diff --git a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java index 44708dfcc7f..4d138f2ed69 100644 --- a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java @@ -45,7 +45,7 @@ public LspClientHandler(RemoteMessageHandler messageHandler, CliPreferences cliP this.settings = ExtensionSettings.getDefaultSettings(); this.parserHandler = new LspParserHandler(); this.diagnosticHandler = new LspDiagnosticHandler(this, parserHandler, cliPreferences, abbreviationRepository); - this.linkHandler = new LspLinkHandler(parserHandler); + this.linkHandler = new LspLinkHandler(parserHandler, cliPreferences); this.workspaceService = new BibtexWorkspaceService(this, diagnosticHandler); this.textDocumentService = new BibtexTextDocumentService(messageHandler, this, diagnosticHandler, linkHandler); this.messageHandler = messageHandler; diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java index 582801766d4..aae91406499 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java @@ -1,16 +1,21 @@ package org.jabref.languageserver.util; import java.util.List; +import java.util.stream.Stream; +import org.jabref.languageserver.LspClientHandler; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.integrity.IntegrityCheck; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.preferences.CliPreferences; import org.eclipse.lsp4j.Diagnostic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LspIntegrityCheck { + private static final Logger LOGGER = LoggerFactory.getLogger(LspIntegrityCheck.class); private static final boolean ALLOW_INTEGER_EDITION = true; private final CliPreferences cliPreferences; @@ -30,12 +35,19 @@ public List check(ParserResult parserResult) { ALLOW_INTEGER_EDITION ); - return parserResult.getDatabaseContext().getEntries().stream().flatMap(entry -> integrityCheck.checkEntry(entry).stream().map(message -> { - if (entry.getFieldOrAlias(message.field()).isPresent()) { - return LspDiagnosticBuilder.create(parserResult, message.message()).setField(message.field()).setEntry(entry).build(); - } else { - return LspDiagnosticBuilder.create(parserResult, message.message()).setEntry(entry).build(); + return parserResult.getDatabaseContext().getEntries().stream().flatMap(entry -> { + try { + return integrityCheck.checkEntry(entry).stream().map(message -> { + if (entry.getFieldOrAlias(message.field()).isPresent()) { + return LspDiagnosticBuilder.create(parserResult, message.message()).setField(message.field()).setEntry(entry).build(); + } else { + return LspDiagnosticBuilder.create(parserResult, message.message()).setEntry(entry).build(); + } + }); + } catch (NullPointerException nullPointerException) { + LOGGER.error("Error while performing integrity check.", nullPointerException); } - })).toList(); + return Stream.of(); + }).toList(); } } diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java index d32fad25d1d..e71fdc41780 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java @@ -6,6 +6,7 @@ import org.jabref.languageserver.util.definition.DefinitionProvider; import org.jabref.languageserver.util.definition.DefinitionProviderFactory; +import org.jabref.logic.preferences.CliPreferences; import org.eclipse.lsp4j.DocumentLink; import org.eclipse.lsp4j.Location; @@ -20,26 +21,28 @@ public class LspLinkHandler { private static final Logger LOGGER = LoggerFactory.getLogger(LspLinkHandler.class); private final LspParserHandler parserHandler; + private final CliPreferences preferences; - public LspLinkHandler(LspParserHandler parserHandler) { + public LspLinkHandler(LspParserHandler parserHandler, CliPreferences preferences) { this.parserHandler = parserHandler; + this.preferences = preferences; } public CompletableFuture, List>> provideDefinition(String languageId, String uri, String content, Position position) { List locations = List.of(); - Optional provider = DefinitionProviderFactory.getDefinitionProvider(parserHandler, languageId); + Optional provider = DefinitionProviderFactory.getDefinitionProvider(preferences, parserHandler, languageId); if (provider.isPresent()) { - locations = provider.get().provideDefinition(content, position); + locations = provider.get().provideDefinition(uri, content, position); } Either, List> toReturn = Either.forLeft(locations); return CompletableFuture.completedFuture(toReturn); } - public CompletableFuture> provideDocumentLinks(String languageId, String content) { + public CompletableFuture> provideDocumentLinks(String fileUri, String languageId, String content) { List documentLinks = List.of(); - Optional provider = DefinitionProviderFactory.getDefinitionProvider(parserHandler, languageId); + Optional provider = DefinitionProviderFactory.getDefinitionProvider(preferences, parserHandler, languageId); if (provider.isPresent()) { - documentLinks = provider.get().provideDocumentLinks(content); + documentLinks = provider.get().provideDocumentLinks(fileUri, content); } return CompletableFuture.completedFuture(documentLinks); } diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java new file mode 100644 index 00000000000..76b84a9f202 --- /dev/null +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -0,0 +1,96 @@ +package org.jabref.languageserver.util.definition; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.jabref.languageserver.util.LspParserHandler; +import org.jabref.languageserver.util.LspRangeUtil; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.util.FileFieldParser; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.field.StandardField; + +import org.eclipse.lsp4j.DocumentLink; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BibDefinitionProvider extends DefinitionProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(BibDefinitionProvider.class); + private static final Range EMPTY_RANGE = new Range(new Position(0, 0), new Position(0, 0)); + + private final CliPreferences preferences; + + public BibDefinitionProvider(CliPreferences preferences, LspParserHandler parserHandler) { + super(parserHandler); + this.preferences = preferences; + } + + @Override + public List provideDefinition(String uri, String content, Position position) { + Optional parserResult = parserHandler.getParserResultForUri(uri); + + if (parserResult.isEmpty()) { + return List.of(); + } + + for (Map.Entry entry : parserResult.get().getArticleRanges().entrySet()) { + BibEntry bibEntry = entry.getKey(); + ParserResult.Range range = entry.getValue(); + if (bibEntry.getField(StandardField.FILE).isPresent() && LspRangeUtil.isPositionInRange(position, LspRangeUtil.convertToLspRange(range))) { + Range fileFieldRange = LspRangeUtil.convertToLspRange(parserResult.get().getFieldRange(bibEntry, StandardField.FILE)); + if (!LspRangeUtil.isPositionInRange(position, fileFieldRange)) { + return List.of(); + } + Optional fileString = bibEntry.getFieldOrAlias(StandardField.FILE); + if (fileString.isEmpty()) { + return List.of(); + } + + int offsetStart = LspRangeUtil.toOffset(content, fileFieldRange.getStart()); + int offsetEnd = LspRangeUtil.toOffset(content, fileFieldRange.getEnd()); + String fileField = content.substring(offsetStart, offsetEnd); + int startIndex = offsetStart + fileField.indexOf(fileString.get()); + + Map fileRangeMap = FileFieldParser.parseToPosition(fileString.get()); + for (Map.Entry linkedFileRangeEntry : fileRangeMap.entrySet()) { + LinkedFile linkedFile = linkedFileRangeEntry.getKey(); + org.jabref.model.util.Range rangeInFileString = linkedFileRangeEntry.getValue(); + int start = startIndex + rangeInFileString.start(); + int end = start + rangeInFileString.end(); + Range linkRange = LspRangeUtil.convertToLspRange(content, start, end); + if (LspRangeUtil.isPositionInRange(position, linkRange)) { + try { + Optional filePath = FileUtil.find(parserResult.get().getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); + return List.of(new Location(filePath.get().toUri().toString(), EMPTY_RANGE)); + } catch (NullPointerException e) { + LOGGER.error("Error while getting file path", e); + } + } + } + } + } + return List.of(); + } + + @Override + public List provideDocumentLinks(String fileUri, String content) { + List documentLinks = new ArrayList<>(); + parserHandler.getParserResultForUri(fileUri).ifPresent(parserResult -> { + parserResult.getDatabaseContext().getEntries().getFirst().getFiles().forEach(linkedFile -> { + + }); + }); + + return documentLinks; + } +} diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java index 2605c142919..ef56e657539 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java @@ -29,7 +29,7 @@ public DefinitionProvider(LspParserHandler parserHandler) { this.parserHandler = parserHandler; } - public List provideDefinition(String content, Position position) { + public List provideDefinition(String uri, String content, Position position) { Optional citationKey = getCitationKeyAtPosition(content, position); if (citationKey.isPresent()) { Map> entriesMap = parserHandler.searchForEntryByCitationKey(citationKey.get()); @@ -43,7 +43,7 @@ public List provideDefinition(String content, Position position) { return List.of(); } - public List provideDocumentLinks(String content) { + public List provideDocumentLinks(String fileUri, String content) { if (content == null || content.isEmpty()) { return List.of(); } diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java index 4f8af0043d4..aeb3f4be418 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java @@ -5,17 +5,20 @@ import java.util.Optional; import org.jabref.languageserver.util.LspParserHandler; +import org.jabref.logic.preferences.CliPreferences; public class DefinitionProviderFactory { private static final Map PROVIDER_MAP = new HashMap<>(); - public static Optional getDefinitionProvider(LspParserHandler parserHandler, String languageId) { + public static Optional getDefinitionProvider(CliPreferences preferences, LspParserHandler parserHandler, String languageId) { return Optional.ofNullable(PROVIDER_MAP.computeIfAbsent(languageId.toLowerCase(), key -> switch (key) { case "markdown" -> new MarkdownDefinitionProvider(parserHandler); case "latex" -> new LatexDefinitionProvider(parserHandler); + case "bibtex" -> + new BibDefinitionProvider(preferences, parserHandler); default -> null; })); diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitionProvider.java index d5fd8df3cfa..1b42cd82392 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitionProvider.java @@ -23,13 +23,13 @@ public LatexDefinitionProvider(LspParserHandler parserHandler) { } @Override - public List provideDefinition(String content, Position position) { + public List provideDefinition(String uri, String content, Position position) { List locations = new ArrayList<>(); latexParser.parse(content).getCitations().forEach((key, citation) -> { Range range = LspRangeUtil.convertToLspRange(citation.line(), citation.colStart(), citation.colEnd()); if (LspRangeUtil.isPositionInRange(position, range)) { - parserHandler.searchForEntryByCitationKey(key).forEach((uri, entries) -> entries.forEach(entry -> { - locations.add(createLocation(getRangeFromEntry(uri, entry), uri)); + parserHandler.searchForEntryByCitationKey(key).forEach((databaseUri, entries) -> entries.forEach(entry -> { + locations.add(createLocation(getRangeFromEntry(databaseUri, entry), databaseUri)); })); } }); @@ -37,7 +37,7 @@ public List provideDefinition(String content, Position position) { } @Override - public List provideDocumentLinks(String content) { + public List provideDocumentLinks(String fileUri, String content) { List locations = new ArrayList<>(); latexParser.parse(content).getCitations().forEach((key, citation) -> { locations.add(createDocumentLink(LspRangeUtil.convertToLspRange(citation.line(), citation.colStart(), citation.colEnd()), key)); From 3d7c63876cf10935bcc831ea18a92a3b9deaa300 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:36:17 +0100 Subject: [PATCH 02/21] fix jbang --- .jbang/JabLsLauncher.java | 1 + 1 file changed, 1 insertion(+) mode change 100755 => 100644 .jbang/JabLsLauncher.java diff --git a/.jbang/JabLsLauncher.java b/.jbang/JabLsLauncher.java old mode 100755 new mode 100644 index d1738b7299f..88317328d40 --- a/.jbang/JabLsLauncher.java +++ b/.jbang/JabLsLauncher.java @@ -21,6 +21,7 @@ //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspRangeUtil.java +//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java //SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitonProvider.java From 714e3c755a6fca941c8a18f5e2ecd15dae26cac4 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:36:54 +0100 Subject: [PATCH 03/21] fix format --- .../java/org/jabref/logic/importer/util/FileFieldParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index 931e6b8e1ac..52676eabf9c 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -204,5 +204,6 @@ static LinkedFile convert(List entry) { return field; } - private record LinkedFilePosition(LinkedFile linkedFile, Range range) { } + private record LinkedFilePosition(LinkedFile linkedFile, Range range) { + } } From 9a3113854b98c69aae84175dfccd240639d2d12d Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:10:55 +0100 Subject: [PATCH 04/21] fix checkstyle --- .../jabref/languageserver/util/LspIntegrityCheck.java | 1 - .../util/definition/BibDefinitionProvider.java | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java index aae91406499..a0f8ff6754f 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.stream.Stream; -import org.jabref.languageserver.LspClientHandler; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.integrity.IntegrityCheck; import org.jabref.logic.journals.JournalAbbreviationRepository; diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index 76b84a9f202..cdbcde7fbf8 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -84,13 +84,6 @@ public List provideDefinition(String uri, String content, Position pos @Override public List provideDocumentLinks(String fileUri, String content) { - List documentLinks = new ArrayList<>(); - parserHandler.getParserResultForUri(fileUri).ifPresent(parserResult -> { - parserResult.getDatabaseContext().getEntries().getFirst().getFiles().forEach(linkedFile -> { - - }); - }); - - return documentLinks; + return List.of(); } } From 93a8bad09ba2e7d671248d60dea9cbac2dd66002 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:16:49 +0100 Subject: [PATCH 05/21] fix tests --- .../java/org/jabref/logic/importer/util/FileFieldParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index 52676eabf9c..b2e042d2a83 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -49,7 +49,7 @@ private FileFieldParser(String value) { public static List parse(String value) { // We need state to have a more clean code. Thus, we instantiate the class and then return the result FileFieldParser fileFieldParser = new FileFieldParser(value); - return fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).toList(); + return new ArrayList<>(fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).toList()); } public static Map parseToPosition(String value) { From b3cf1328c4c8384d52ea0dec4fa580a6049efde9 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:20:50 +0100 Subject: [PATCH 06/21] remove unused import --- .../languageserver/util/definition/BibDefinitionProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index cdbcde7fbf8..baed638e791 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -1,7 +1,6 @@ package org.jabref.languageserver.util.definition; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; From 13e1d1ed30eac3e954da49b66d21b38f699702a9 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 9 Nov 2025 21:05:24 +0100 Subject: [PATCH 07/21] Add debug --- .github/workflows/tests-code.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests-code.yml b/.github/workflows/tests-code.yml index 7fce6d27f6b..a3049f86703 100644 --- a/.github/workflows/tests-code.yml +++ b/.github/workflows/tests-code.yml @@ -568,6 +568,7 @@ jobs: esac echo "//SOURCES ../$f" >> "${{ matrix.script }}" done + - run: cat ${{ matrix.script }} - run: jbang build "${{ matrix.script }}" shell: bash - run: jbang "${{ matrix.script }}" --help From 4956a8697ab2db485688b5379f4b2e43cd2626f3 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 10:45:02 +0100 Subject: [PATCH 08/21] .jbang scripts should not be included inside themselves --- .github/workflows/tests-code.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-code.yml b/.github/workflows/tests-code.yml index a3049f86703..dabbdf620b6 100644 --- a/.github/workflows/tests-code.yml +++ b/.github/workflows/tests-code.yml @@ -555,12 +555,16 @@ jobs: # We modify the JBang scripts directly to avoid issues with relative paths for f in ${{ steps.changed-jablib-files.outputs.all_changed_files }}; do case "$f" in + .jbang/*) + # skip scripts + continue + ;; jablib-examples/*) # skip scripts continue ;; jabkit/*) - # only JabKit needs its modified sources + # only JabKit needs its modified sources if jabkit was modified if [ "${{ matrix.script }}" != ".jbang/JabKitLauncher.java" ]; then continue fi From 8d071045f14ff5c7efe460388c7954ecb9f2c03d Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:03:21 +0100 Subject: [PATCH 09/21] Add ability to open pdfs in standalone nad gui mode --- .../languageserver/BibtexTextDocumentService.java | 6 ------ .../org/jabref/languageserver/LspClientHandler.java | 2 +- .../languageserver/util/LspIntegrityCheck.java | 2 +- .../jabref/languageserver/util/LspLinkHandler.java | 12 +++++++++++- .../util/definition/BibDefinitionProvider.java | 3 ++- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java b/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java index 4e883dd158f..e30f0cda764 100644 --- a/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java +++ b/jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java @@ -94,9 +94,6 @@ public void didSave(DidSaveTextDocumentParams params) { @Override public CompletableFuture, List>> definition(DefinitionParams params) { - if (!clientHandler.isStandalone()) { - return CompletableFuture.completedFuture(Either.forLeft(List.of())); - } if (fileUriToLanguageId.containsKey(params.getTextDocument().getUri())) { String fileUri = params.getTextDocument().getUri(); return linkHandler.provideDefinition(fileUriToLanguageId.get(fileUri), fileUri, contentCache.get(fileUri), params.getPosition()); @@ -106,9 +103,6 @@ public CompletableFuture, List> documentLink(DocumentLinkParams params) { - if (clientHandler.isStandalone()) { - return CompletableFuture.completedFuture(List.of()); - } String fileUri = params.getTextDocument().getUri(); return linkHandler.provideDocumentLinks(fileUri, fileUriToLanguageId.get(fileUri), contentCache.get(fileUri)); } diff --git a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java index 4d138f2ed69..4f075b3ecbf 100644 --- a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java @@ -45,7 +45,7 @@ public LspClientHandler(RemoteMessageHandler messageHandler, CliPreferences cliP this.settings = ExtensionSettings.getDefaultSettings(); this.parserHandler = new LspParserHandler(); this.diagnosticHandler = new LspDiagnosticHandler(this, parserHandler, cliPreferences, abbreviationRepository); - this.linkHandler = new LspLinkHandler(parserHandler, cliPreferences); + this.linkHandler = new LspLinkHandler(this, parserHandler, cliPreferences); this.workspaceService = new BibtexWorkspaceService(this, diagnosticHandler); this.textDocumentService = new BibtexTextDocumentService(messageHandler, this, diagnosticHandler, linkHandler); this.messageHandler = messageHandler; diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java index a0f8ff6754f..301ab9c4fe4 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java @@ -44,7 +44,7 @@ public List check(ParserResult parserResult) { } }); } catch (NullPointerException nullPointerException) { - LOGGER.error("Error while performing integrity check.", nullPointerException); + LOGGER.debug("Error while performing integrity check.", nullPointerException); } return Stream.of(); }).toList(); diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java index e71fdc41780..1aa6934c6d3 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java @@ -4,6 +4,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.jabref.languageserver.LspClientHandler; import org.jabref.languageserver.util.definition.DefinitionProvider; import org.jabref.languageserver.util.definition.DefinitionProviderFactory; import org.jabref.logic.preferences.CliPreferences; @@ -20,15 +21,21 @@ public class LspLinkHandler { private static final Logger LOGGER = LoggerFactory.getLogger(LspLinkHandler.class); + private final LspClientHandler clientHandler; private final LspParserHandler parserHandler; private final CliPreferences preferences; - public LspLinkHandler(LspParserHandler parserHandler, CliPreferences preferences) { + public LspLinkHandler(LspClientHandler clientHandler, LspParserHandler parserHandler, CliPreferences preferences) { + this.clientHandler = clientHandler; this.parserHandler = parserHandler; this.preferences = preferences; } public CompletableFuture, List>> provideDefinition(String languageId, String uri, String content, Position position) { + if (!clientHandler.isStandalone() && !"bibtex".equals(languageId)) { + return CompletableFuture.completedFuture(Either.forLeft(List.of())); + } + List locations = List.of(); Optional provider = DefinitionProviderFactory.getDefinitionProvider(preferences, parserHandler, languageId); if (provider.isPresent()) { @@ -39,6 +46,9 @@ public CompletableFuture, List> provideDocumentLinks(String fileUri, String languageId, String content) { + if (clientHandler.isStandalone()) { + return CompletableFuture.completedFuture(List.of()); + } List documentLinks = List.of(); Optional provider = DefinitionProviderFactory.getDefinitionProvider(preferences, parserHandler, languageId); if (provider.isPresent()) { diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index baed638e791..2a043d33f75 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -72,7 +72,7 @@ public List provideDefinition(String uri, String content, Position pos Optional filePath = FileUtil.find(parserResult.get().getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); return List.of(new Location(filePath.get().toUri().toString(), EMPTY_RANGE)); } catch (NullPointerException e) { - LOGGER.error("Error while getting file path", e); + LOGGER.debug("Error while getting file path", e); } } } @@ -81,6 +81,7 @@ public List provideDefinition(String uri, String content, Position pos return List.of(); } + // Not needed when trying to resolve links to pdfs @Override public List provideDocumentLinks(String fileUri, String content) { return List.of(); From 99b46cd2e81a75d046f2a8411814fa4d0463a2f4 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:15:26 +0100 Subject: [PATCH 10/21] update execution rights --- .jbang/JabLsLauncher.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .jbang/JabLsLauncher.java diff --git a/.jbang/JabLsLauncher.java b/.jbang/JabLsLauncher.java old mode 100644 new mode 100755 From d618738da0083d8ac58f28b0f94ac2416d016f3b Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 12:39:31 +0100 Subject: [PATCH 11/21] More `@NonNull` --- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index 16f7c9ce114..e806fb80cf5 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -403,9 +403,9 @@ public static Optional findSingleFileRecursively(String filename, Path roo return Optional.empty(); } - public static Optional find(final BibDatabaseContext databaseContext, + public static Optional find(@NonNull BibDatabaseContext databaseContext, @NonNull String fileName, - FilePreferences filePreferences) { + @NonNull FilePreferences filePreferences) { return find(fileName, databaseContext.getFileDirectories(filePreferences)); } @@ -416,7 +416,7 @@ public static Optional find(final BibDatabaseContext databaseContext, * Will look in each of the given directories starting from the beginning and * returning the first found file to match if any. */ - public static Optional find(String fileName, List directories) { + public static Optional find(@NonNull String fileName, @NonNull List directories) { if (directories.isEmpty()) { // Fallback, if no directories to resolve are passed Path path = Path.of(fileName); From 5b0aa6672707c38834c209fe84252985ca82c813 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 12:52:32 +0100 Subject: [PATCH 12/21] More `@NonNull` --- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java | 2 +- .../main/java/org/jabref/model/database/BibDatabaseContext.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index e806fb80cf5..52fa90fa3b9 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -416,7 +416,7 @@ public static Optional find(@NonNull BibDatabaseContext databaseContext, * Will look in each of the given directories starting from the beginning and * returning the first found file to match if any. */ - public static Optional find(@NonNull String fileName, @NonNull List directories) { + public static Optional find(@NonNull String fileName, @NonNull List<@NonNull Path> directories) { if (directories.isEmpty()) { // Fallback, if no directories to resolve are passed Path path = Path.of(fileName); diff --git a/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java index d031935936b..0c35986fe8b 100644 --- a/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -167,7 +167,7 @@ public boolean isStudy() { * @param preferences The fileDirectory preferences * @return List of existing absolute paths */ - public List getFileDirectories(FilePreferences preferences) { + public @NonNull List<@NonNull Path> getFileDirectories(@NonNull FilePreferences preferences) { // Paths are a) ordered and b) should be contained only once in the result LinkedHashSet fileDirs = new LinkedHashSet<>(3); From 8cfe32f4d0b433532513af8f1d68bb4714f7b0ee Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 12:52:40 +0100 Subject: [PATCH 13/21] Fix data type --- .../java/org/jabref/model/database/BibDatabaseContext.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java index 0c35986fe8b..e2fc3a75705 100644 --- a/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.SequencedCollection; import java.util.UUID; import org.jabref.architecture.AllowedToUseLogic; @@ -169,7 +170,7 @@ public boolean isStudy() { */ public @NonNull List<@NonNull Path> getFileDirectories(@NonNull FilePreferences preferences) { // Paths are a) ordered and b) should be contained only once in the result - LinkedHashSet fileDirs = new LinkedHashSet<>(3); + SequencedCollection fileDirs = new LinkedHashSet<>(3); Optional userFileDirectory = metaData.getUserFileDirectory(preferences.getUserAndHost()).map(this::getFileDirectoryPath); userFileDirectory.ifPresent(fileDirs::add); From cc75b7a91af37a45a3637f3aeecd7b2376e815be Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 12:57:59 +0100 Subject: [PATCH 14/21] Fix catch of NPE in context of Optionals --- .../util/definition/BibDefinitionProvider.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index 2a043d33f75..a151ad30978 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -68,12 +68,13 @@ public List provideDefinition(String uri, String content, Position pos int end = start + rangeInFileString.end(); Range linkRange = LspRangeUtil.convertToLspRange(content, start, end); if (LspRangeUtil.isPositionInRange(position, linkRange)) { - try { - Optional filePath = FileUtil.find(parserResult.get().getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); - return List.of(new Location(filePath.get().toUri().toString(), EMPTY_RANGE)); - } catch (NullPointerException e) { - LOGGER.debug("Error while getting file path", e); + Optional filePath = FileUtil.find(parserResult.get().getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); + if (LOGGER.isDebugEnabled() && filePath.isEmpty()) { + LOGGER.debug("filePath is empty"); } + return filePath + .map(p -> List.of(new Location(p.toUri().toString(), EMPTY_RANGE))) + .orElse(List.of()); } } } From 08f90e57678221737fc75e876a2bd4deaf7ad201 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 17:42:11 +0100 Subject: [PATCH 15/21] Add `@NullMarked` and refactor Optional use --- .../util/definition/BibDefinitionProvider.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index a151ad30978..070850b4359 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -19,9 +19,11 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; +import org.jspecify.annotations.NullMarked; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@NullMarked public class BibDefinitionProvider extends DefinitionProvider { private static final Logger LOGGER = LoggerFactory.getLogger(BibDefinitionProvider.class); @@ -36,17 +38,18 @@ public BibDefinitionProvider(CliPreferences preferences, LspParserHandler parser @Override public List provideDefinition(String uri, String content, Position position) { - Optional parserResult = parserHandler.getParserResultForUri(uri); - - if (parserResult.isEmpty()) { + Optional parserResultOpt = parserHandler.getParserResultForUri(uri); + if (parserResultOpt.isEmpty()) { return List.of(); } - for (Map.Entry entry : parserResult.get().getArticleRanges().entrySet()) { + ParserResult parserResult = parserResultOpt.get(); + + for (Map.Entry entry : parserResult.getArticleRanges().entrySet()) { BibEntry bibEntry = entry.getKey(); ParserResult.Range range = entry.getValue(); if (bibEntry.getField(StandardField.FILE).isPresent() && LspRangeUtil.isPositionInRange(position, LspRangeUtil.convertToLspRange(range))) { - Range fileFieldRange = LspRangeUtil.convertToLspRange(parserResult.get().getFieldRange(bibEntry, StandardField.FILE)); + Range fileFieldRange = LspRangeUtil.convertToLspRange(parserResult.getFieldRange(bibEntry, StandardField.FILE)); if (!LspRangeUtil.isPositionInRange(position, fileFieldRange)) { return List.of(); } @@ -68,7 +71,7 @@ public List provideDefinition(String uri, String content, Position pos int end = start + rangeInFileString.end(); Range linkRange = LspRangeUtil.convertToLspRange(content, start, end); if (LspRangeUtil.isPositionInRange(position, linkRange)) { - Optional filePath = FileUtil.find(parserResult.get().getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); + Optional filePath = FileUtil.find(parserResult.getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); if (LOGGER.isDebugEnabled() && filePath.isEmpty()) { LOGGER.debug("filePath is empty"); } From 6a26f18754103a64d0b45da7ccd04fe4c2f10ef7 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 20:37:21 +0100 Subject: [PATCH 16/21] More close constructors --- .../java/org/jabref/languageserver/LspClientHandler.java | 2 +- .../org/jabref/languageserver/util/LspLinkHandler.java | 6 +++--- .../util/definition/BibDefinitionProvider.java | 8 ++++---- .../util/definition/DefinitionProviderFactory.java | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java index 4f075b3ecbf..4121b8011c1 100644 --- a/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java @@ -45,7 +45,7 @@ public LspClientHandler(RemoteMessageHandler messageHandler, CliPreferences cliP this.settings = ExtensionSettings.getDefaultSettings(); this.parserHandler = new LspParserHandler(); this.diagnosticHandler = new LspDiagnosticHandler(this, parserHandler, cliPreferences, abbreviationRepository); - this.linkHandler = new LspLinkHandler(this, parserHandler, cliPreferences); + this.linkHandler = new LspLinkHandler(this, parserHandler, cliPreferences.getFilePreferences()); this.workspaceService = new BibtexWorkspaceService(this, diagnosticHandler); this.textDocumentService = new BibtexTextDocumentService(messageHandler, this, diagnosticHandler, linkHandler); this.messageHandler = messageHandler; diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java index 1aa6934c6d3..91946c1f41f 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java @@ -7,7 +7,7 @@ import org.jabref.languageserver.LspClientHandler; import org.jabref.languageserver.util.definition.DefinitionProvider; import org.jabref.languageserver.util.definition.DefinitionProviderFactory; -import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.FilePreferences; import org.eclipse.lsp4j.DocumentLink; import org.eclipse.lsp4j.Location; @@ -23,9 +23,9 @@ public class LspLinkHandler { private final LspClientHandler clientHandler; private final LspParserHandler parserHandler; - private final CliPreferences preferences; + private final FilePreferences preferences; - public LspLinkHandler(LspClientHandler clientHandler, LspParserHandler parserHandler, CliPreferences preferences) { + public LspLinkHandler(LspClientHandler clientHandler, LspParserHandler parserHandler, FilePreferences preferences) { this.clientHandler = clientHandler; this.parserHandler = parserHandler; this.preferences = preferences; diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java index 070850b4359..99be04a1e24 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java @@ -7,9 +7,9 @@ import org.jabref.languageserver.util.LspParserHandler; import org.jabref.languageserver.util.LspRangeUtil; +import org.jabref.logic.FilePreferences; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.util.FileFieldParser; -import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; @@ -29,9 +29,9 @@ public class BibDefinitionProvider extends DefinitionProvider { private static final Logger LOGGER = LoggerFactory.getLogger(BibDefinitionProvider.class); private static final Range EMPTY_RANGE = new Range(new Position(0, 0), new Position(0, 0)); - private final CliPreferences preferences; + private final FilePreferences preferences; - public BibDefinitionProvider(CliPreferences preferences, LspParserHandler parserHandler) { + public BibDefinitionProvider(FilePreferences preferences, LspParserHandler parserHandler) { super(parserHandler); this.preferences = preferences; } @@ -71,7 +71,7 @@ public List provideDefinition(String uri, String content, Position pos int end = start + rangeInFileString.end(); Range linkRange = LspRangeUtil.convertToLspRange(content, start, end); if (LspRangeUtil.isPositionInRange(position, linkRange)) { - Optional filePath = FileUtil.find(parserResult.getDatabaseContext(), linkedFile.getLink(), preferences.getFilePreferences()); + Optional filePath = FileUtil.find(parserResult.getDatabaseContext(), linkedFile.getLink(), preferences); if (LOGGER.isDebugEnabled() && filePath.isEmpty()) { LOGGER.debug("filePath is empty"); } diff --git a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java index aeb3f4be418..34d33d185dc 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java @@ -5,13 +5,13 @@ import java.util.Optional; import org.jabref.languageserver.util.LspParserHandler; -import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.FilePreferences; public class DefinitionProviderFactory { private static final Map PROVIDER_MAP = new HashMap<>(); - public static Optional getDefinitionProvider(CliPreferences preferences, LspParserHandler parserHandler, String languageId) { + public static Optional getDefinitionProvider(FilePreferences preferences, LspParserHandler parserHandler, String languageId) { return Optional.ofNullable(PROVIDER_MAP.computeIfAbsent(languageId.toLowerCase(), key -> switch (key) { case "markdown" -> new MarkdownDefinitionProvider(parserHandler); From 2bb3f846184a4ba62ccce776b2ec964050de9f68 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 21:34:15 +0100 Subject: [PATCH 17/21] Add link to other tests --- .../test/java/org/jabref/logic/importer/ParserResultTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jablib/src/test/java/org/jabref/logic/importer/ParserResultTest.java b/jablib/src/test/java/org/jabref/logic/importer/ParserResultTest.java index ff32092d9a1..4fa645cee8f 100644 --- a/jablib/src/test/java/org/jabref/logic/importer/ParserResultTest.java +++ b/jablib/src/test/java/org/jabref/logic/importer/ParserResultTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +// Other tests for reading can be found at [org.jabref.logic.importer.fileformat.BibtexImporterTest] class ParserResultTest { @Test void isEmptyForNewParseResult() { From a1242d7652a16ebd3722e7fd0c2ef0fe2405a10d Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Nov 2025 21:34:24 +0100 Subject: [PATCH 18/21] Output field at parsing --- .../logic/importer/fileformat/BibtexParser.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 5dee7354cac..30ec90d5b60 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -88,6 +88,8 @@ /// /// **Opposite class:** /// [`BibDatabaseWriter`](org.jabref.logic.exporter.BibDatabaseWriter) +/// +/// FIXME: This class relies on `char`, but should use [java.lang.Character] to be fully Unicode compliant. public class BibtexParser implements Parser { private static final Logger LOGGER = LoggerFactory.getLogger(BibtexParser.class); private static final int LOOKAHEAD = 1024; @@ -759,7 +761,7 @@ private void parseField(BibEntry entry) throws IOException { Field field = FieldFactory.parseField(parseTextToken()); skipWhitespace(); - consume('='); + consume(field, '='); String content = parseFieldContent(field); if (!content.isEmpty()) { if (entry.hasField(field)) { @@ -1172,6 +1174,19 @@ private void consume(char expected) throws IOException { } } + private void consume(Field field, char expected) throws IOException { + int character = read(); + + if (character != expected) { + throw new IOException( + "Error at line " + line + + " after column " + column + + " (" + field.getName() + "): Expected " + + expected + " but received " + + (char) character + " (" + character + ")"); + } + } + private boolean consumeUncritically(char expected) throws IOException { int character; // @formatter:off From 47f02c17e5b4947b9b7bd89afc2dfc5844465324 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 11 Nov 2025 07:54:52 +0100 Subject: [PATCH 19/21] Initial test for BibDefinitionProvider --- jabls/build.gradle.kts | 3 + .../definition/BibDefinitionProviderTest.java | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java diff --git a/jabls/build.gradle.kts b/jabls/build.gradle.kts index cb0e1940d68..26fb242ce5c 100644 --- a/jabls/build.gradle.kts +++ b/jabls/build.gradle.kts @@ -18,10 +18,13 @@ dependencies { // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) testImplementation("org.slf4j:jul-to-slf4j") + + testImplementation("org.mockito:mockito-core") } javaModuleTesting.whitebox(testing.suites["test"]) { requires.add("org.junit.jupiter.api") + requires.add("org.mockito") } tasks.test { diff --git a/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java new file mode 100644 index 00000000000..0a95d39ac4f --- /dev/null +++ b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java @@ -0,0 +1,59 @@ +package org.jabref.languageserver.util.definition; + +import java.io.IOException; +import java.util.List; + +import org.jabref.languageserver.util.LspParserHandler; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.JabRefException; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ParserResult; +import org.jabref.model.entry.BibEntryPreferences; +import org.jabref.model.entry.LinkedFile; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BibDefinitionProviderTest { + + private LspParserHandler lspParserHandler = new LspParserHandler(); + private ImportFormatPreferences importFormatPreferences; + + @BeforeEach + void setUp() { + importFormatPreferences = mock(ImportFormatPreferences.class); + when(importFormatPreferences.bibEntryPreferences()).thenReturn(mock(BibEntryPreferences.class)); + when(importFormatPreferences.bibEntryPreferences().getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.filePreferences()).thenReturn(mock(FilePreferences.class)); + when(importFormatPreferences.filePreferences().getUserAndHost()).thenReturn("MockedUser-mockedhost"); + } + + @Test + void provideDefinition() throws JabRefException, IOException { + ParserResult parserResult = lspParserHandler.parserResultFromString( + "some-uri", + """ + @Article{Cooper_2007, + author = {Cooper, Karen A. and Donovan, Jennifer L. and Waterhouse, Andrew L. and Williamson, Gary}, + date = {2007-08}, + journaltitle = {British Journal of Nutrition}, + title = {Cocoa and health: a decade of research}, + doi = {10.1017/s0007114507795296}, + issn = {1475-2662}, + number = {1}, + pages = {1--11}, + volume = {99}, + file = {:C\\\\:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF;:corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF}, + publisher = {Cambridge University Press (CUP)} + , + } + """, + importFormatPreferences); + List files = parserResult.getDatabaseContext().getEntries().getFirst().getFiles(); + assertNotNull(files.getLast().getLink()); + } +} From e982620ee5b9e5fde714d91854aa727438a53f55 Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:40:02 +0100 Subject: [PATCH 20/21] address comments --- .../java/org/jabref/logic/importer/util/FileFieldParser.java | 3 ++- .../util/definition/BibDefinitionProviderTest.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index b2e042d2a83..8d11b855880 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.jabref.logic.util.URLUtil; import org.jabref.model.entry.LinkedFile; @@ -49,7 +50,7 @@ private FileFieldParser(String value) { public static List parse(String value) { // We need state to have a more clean code. Thus, we instantiate the class and then return the result FileFieldParser fileFieldParser = new FileFieldParser(value); - return new ArrayList<>(fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).toList()); + return fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).collect(Collectors.toList()); } public static Map parseToPosition(String value) { diff --git a/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java index 0a95d39ac4f..276a5ee0ca9 100644 --- a/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java +++ b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -54,6 +55,7 @@ void provideDefinition() throws JabRefException, IOException { """, importFormatPreferences); List files = parserResult.getDatabaseContext().getEntries().getFirst().getFiles(); + assertEquals(2, files.size()); assertNotNull(files.getLast().getLink()); } } From 07ae63b0b66fd063c0e327cef4670cd99cc21fc8 Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:35:15 +0100 Subject: [PATCH 21/21] Fix file path on databasecontext --- .../src/main/java/org/jabref/model/entry/LinkedFile.java | 7 +++---- .../jabref/logic/importer/util/FileFieldParserTest.java | 5 +++++ .../org/jabref/languageserver/util/LspParserHandler.java | 8 ++++++++ .../util/definition/BibDefinitionProviderTest.java | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/jablib/src/main/java/org/jabref/model/entry/LinkedFile.java b/jablib/src/main/java/org/jabref/model/entry/LinkedFile.java index e7df19edc6c..4b74ae770a2 100644 --- a/jablib/src/main/java/org/jabref/model/entry/LinkedFile.java +++ b/jablib/src/main/java/org/jabref/model/entry/LinkedFile.java @@ -28,10 +28,9 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; -/** - * Represents the link to an external file (e.g. associated PDF file). - * This class is {@link Serializable} which is needed for drag and drop in gui - */ +/// Represents the link to an external file (e.g. associated PDF file). +/// This class is {@link Serializable} which is needed for drag and drop in gui +/// The conversion from String ([org.jabref.model.entry.field.StandardField.FILE]) is done at [org.jabref.logic.importer.util.FileFieldParser#parse(String)] @AllowedToUseLogic("Uses FileUtil from logic") @NullMarked public class LinkedFile implements Serializable { diff --git a/jablib/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java b/jablib/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java index cede695444d..d535ed27d74 100644 --- a/jablib/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java +++ b/jablib/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java @@ -219,6 +219,11 @@ private static Stream stringsToParseTest() throws MalformedURLExcepti Arguments.of( List.of(new LinkedFile("", "A:\\Zotero\\storage\\test.pdf", "")), "A:\\Zotero\\storage\\test.pdf" + ), + // Mixed path + Arguments.of( + List.of(new LinkedFile("", "C:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf", "")), + ":C\\\\:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF" ) ); } diff --git a/jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java b/jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java index d0bd8cdbb5c..7d6e29bb53c 100644 --- a/jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java +++ b/jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java @@ -2,6 +2,9 @@ import java.io.IOException; import java.io.Reader; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Optional; @@ -22,8 +25,13 @@ public LspParserHandler() { } public ParserResult parserResultFromString(String fileUri, String content, ImportFormatPreferences importFormatPreferences) throws JabRefException, IOException { + // We use BibtexParser directly, because we do not want to add an extra DummyFileMonitor + // Otherwise, we could use `OpenDatabase.loadDatabase(path, importFormatPreferences, new DummyFileUpdateMonitor())` BibtexParser parser = new BibtexParser(importFormatPreferences); ParserResult parserResult = parser.parse(Reader.of(content)); + URI uri = URI.create(fileUri); + Path path = Paths.get(uri); + parserResult.getDatabaseContext().setDatabasePath(path); parserResults.put(fileUri, parserResult); return parserResult; } diff --git a/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java index 276a5ee0ca9..e9884493646 100644 --- a/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java +++ b/jabls/src/test/java/org/jabref/languageserver/util/definition/BibDefinitionProviderTest.java @@ -48,7 +48,7 @@ void provideDefinition() throws JabRefException, IOException { number = {1}, pages = {1--11}, volume = {99}, - file = {:C\\\\:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF;:corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF}, + file = {:C\\:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF;:corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF}, publisher = {Cambridge University Press (CUP)} , }