diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 37a77ac9aee..1d5688e5f7d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -291,7 +291,7 @@ private List createTabs() { tabs.add(sourceTab); // LaTeX citations tab - tabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor)); + tabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); return tabs; } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java index 1e36be7b54a..a1bb0fa7869 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -1,9 +1,9 @@ package org.jabref.gui.entryeditor; -import java.nio.file.Path; import java.util.Optional; import java.util.stream.Collectors; +import javafx.scene.control.Button; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ScrollPane; import javafx.scene.control.Tooltip; @@ -11,6 +11,7 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; +import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; @@ -27,8 +28,8 @@ public class LatexCitationsTab extends EntryEditorTab { private final ProgressIndicator progressIndicator; public LatexCitationsTab(BibDatabaseContext databaseContext, PreferencesService preferencesService, - TaskExecutor taskExecutor) { - this.viewModel = new LatexCitationsTabViewModel(databaseContext, preferencesService, taskExecutor); + TaskExecutor taskExecutor, DialogService dialogService) { + this.viewModel = new LatexCitationsTabViewModel(databaseContext, preferencesService, taskExecutor, dialogService); this.searchPane = new StackPane(); this.progressIndicator = new ProgressIndicator(); @@ -62,14 +63,25 @@ private void setSearchPane() { }); } + private VBox getLatexDirectoryBox() { + Text latexDirectoryText = new Text(String.format("%n%n%s: %s", Localization.lang("Current search directory"), + viewModel.directoryProperty().get())); + latexDirectoryText.setStyle("-fx-font-weight: bold;"); + Button latexDirectoryButton = new Button(Localization.lang("Set LaTeX file directory")); + latexDirectoryButton.setOnAction(event -> viewModel.setLatexDirectory()); + + return new VBox(15, latexDirectoryText, latexDirectoryButton); + } + private ScrollPane getCitationsPane() { Text titleText = new Text(Localization.lang("Citations found")); titleText.getStyleClass().add("recommendation-heading"); VBox citationsBox = new VBox(20, titleText); - Path basePath = viewModel.directoryProperty().get(); citationsBox.getChildren().addAll(viewModel.getCitationList().stream().map( - citation -> citation.getDisplayGraphic(basePath, Optional.empty())).collect(Collectors.toList())); + citation -> citation.getDisplayGraphic(viewModel.directoryProperty().get(), Optional.empty())).collect(Collectors.toList())); + + citationsBox.getChildren().add(getLatexDirectoryBox()); ScrollPane citationsPane = new ScrollPane(); citationsPane.setContent(citationsBox); @@ -82,13 +94,9 @@ private ScrollPane getNotFoundPane() { notFoundTitleText.getStyleClass().add("recommendation-heading"); Text notFoundText = new Text(Localization.lang("No LaTeX files containing this entry were found.")); - notFoundText.setStyle("-fx-font-size: 110%"); - - Text notFoundAdviceText = new Text(Localization.lang( - "You can set the LaTeX file directory in the 'Library properties' dialog.")); - notFoundAdviceText.setStyle("-fx-font-weight: bold;"); + notFoundText.setStyle("-fx-font-size: 110%;"); - VBox notFoundBox = new VBox(20, notFoundTitleText, notFoundText, notFoundAdviceText); + VBox notFoundBox = new VBox(20, notFoundTitleText, notFoundText, getLatexDirectoryBox()); ScrollPane notFoundPane = new ScrollPane(); notFoundPane.setContent(notFoundBox); @@ -103,7 +111,7 @@ private ScrollPane getErrorPane() { Text errorMessageText = new Text(viewModel.searchErrorProperty().get()); errorMessageText.setStyle("-fx-font-family: monospace;-fx-font-size: 120%;"); - VBox errorBox = new VBox(20, errorTitleText, errorMessageText); + VBox errorBox = new VBox(20, errorTitleText, errorMessageText, getLatexDirectoryBox()); ScrollPane errorPane = new ScrollPane(); errorPane.setContent(errorBox); diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java index 239027f68cf..f0bfedd16d3 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -3,7 +3,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -18,8 +20,10 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.DialogService; import org.jabref.gui.texparser.CitationViewModel; import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.logic.texparser.DefaultTexParser; @@ -45,17 +49,20 @@ enum Status { private final BibDatabaseContext databaseContext; private final PreferencesService preferencesService; private final TaskExecutor taskExecutor; + private final DialogService dialogService; private final ObjectProperty directory; private final ObservableList citationList; private final ObjectProperty status; private final StringProperty searchError; private Future searchTask; + private TexParserResult texParserResult; public LatexCitationsTabViewModel(BibDatabaseContext databaseContext, PreferencesService preferencesService, - TaskExecutor taskExecutor) { + TaskExecutor taskExecutor, DialogService dialogService) { this.databaseContext = databaseContext; this.preferencesService = preferencesService; this.taskExecutor = taskExecutor; + this.dialogService = dialogService; this.directory = new SimpleObjectProperty<>(null); this.citationList = FXCollections.observableArrayList(); this.status = new SimpleObjectProperty<>(Status.IN_PROGRESS); @@ -95,7 +102,7 @@ private void startSearch(String citeKey) { .onRunning(() -> status.set(Status.IN_PROGRESS)) .onSuccess(status::set) .onFailure(error -> { - searchError.set(String.format("%s%n%n%s", error.getMessage(), error.getCause())); + searchError.set(error.getMessage()); status.set(Status.ERROR); }) .executeWith(taskExecutor); @@ -111,23 +118,53 @@ private void cancelSearch() { } private Status searchAndParse(String citeKey) throws IOException { - directory.set(databaseContext.getMetaData().getLaTexFileDirectory(preferencesService.getUser()) - .orElseGet(preferencesService::getWorkingDir)); - List texFiles; - try (Stream filesStream = Files.walk(directory.get())) { - texFiles = filesStream.filter(path -> path.toFile().isFile() && path.toString().endsWith(TEX_EXT)) - .collect(Collectors.toList()); - } catch (IOException e) { - LOGGER.error("Error searching files", e); - throw new IOException("Error searching files", e); + Path newDirectory = databaseContext.getMetaData().getLaTexFileDirectory(preferencesService.getUser()) + .orElseGet(preferencesService::getWorkingDir); + + if (texParserResult == null || !newDirectory.equals(directory.get())) { + directory.set(newDirectory); + + if (Files.notExists(newDirectory)) { + throw new IOException(String.format("Current search directory does not exist: %s", newDirectory)); + } + + List texFiles = searchDirectory(newDirectory, new ArrayList<>()); + texParserResult = new DefaultTexParser().parse(texFiles); } - TexParserResult texParserResult = new DefaultTexParser().parse(citeKey, texFiles); - citationList.setAll(texParserResult.getCitations().values().stream().map(CitationViewModel::new).collect(Collectors.toList())); + citationList.setAll(texParserResult.getCitationsByKey(citeKey).stream().map(CitationViewModel::new).collect(Collectors.toList())); return citationList.isEmpty() ? Status.NO_RESULTS : Status.CITATIONS_FOUND; } + private List searchDirectory(Path directory, List texFiles) { + Map> fileListPartition; + try (Stream filesStream = Files.list(directory)) { + fileListPartition = filesStream.collect(Collectors.partitioningBy(path -> path.toFile().isDirectory())); + } catch (IOException e) { + LOGGER.error(String.format("Error searching files: %s", e.getMessage())); + return texFiles; + } + + List subDirectories = fileListPartition.get(true); + List files = fileListPartition.get(false) + .stream() + .filter(path -> path.toString().endsWith(TEX_EXT)) + .collect(Collectors.toList()); + texFiles.addAll(files); + subDirectories.forEach(subDirectory -> searchDirectory(subDirectory, texFiles)); + + return texFiles; + } + + public void setLatexDirectory() { + DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(directory.get()).build(); + + dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> + databaseContext.getMetaData().setLaTexFileDirectory(preferencesService.getUser(), selectedDirectory.toAbsolutePath())); + } + public boolean shouldShow() { return preferencesService.getEntryEditorPreferences().shouldShowLatexCitationsTab(); } diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java b/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java index ef6b25f06f8..7fd3b6eb3a4 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java @@ -34,9 +34,12 @@ import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; import de.saxsys.mvvmfx.utils.validation.ValidationMessage; import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ParseTexDialogViewModel extends AbstractViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ParseTexDialogViewModel.class); private static final String TEX_EXT = ".tex"; private final DialogService dialogService; private final TaskExecutor taskExecutor; @@ -131,7 +134,7 @@ public void searchButtonClicked() { private FileNodeViewModel searchDirectory(Path directory) throws IOException { if (directory == null || !directory.toFile().exists() || !directory.toFile().isDirectory()) { - throw new IOException("Error searching an invalid directory"); + throw new IOException(String.format("Error searching files: %s", directory)); } FileNodeViewModel parent = new FileNodeViewModel(directory); @@ -140,7 +143,8 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { try (Stream filesStream = Files.list(directory)) { fileListPartition = filesStream.collect(Collectors.partitioningBy(path -> path.toFile().isDirectory())); } catch (IOException e) { - throw new IOException("Error searching files", e); + LOGGER.error(String.format("Error searching files: %s", e.getMessage())); + return parent; } List subDirectories = fileListPartition.get(true); diff --git a/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java index c7dcc0d5327..2bd0d6f1bcf 100644 --- a/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java +++ b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.LineNumberReader; +import java.io.UncheckedIOException; import java.nio.channels.ClosedChannelException; import java.nio.file.Files; import java.nio.file.Path; @@ -58,56 +59,47 @@ public TexParserResult getTexParserResult() { @Override public TexParserResult parse(String citeString) { - matchCitation(null, Paths.get(""), 1, citeString); + matchCitation(Paths.get(""), 1, citeString); return texParserResult; } @Override public TexParserResult parse(Path texFile) { - return parse(null, Collections.singletonList(texFile)); + return parse(Collections.singletonList(texFile)); } @Override public TexParserResult parse(List texFiles) { - return parse(null, texFiles); - } - - /** - * Parse a list of TEX files for searching a given entry. - * - * @param entryKey String that contains the entry key we are searching (null for all entries) - * @param texFiles List of Path objects linked to a TEX file - * @return a TexParserResult, which contains all data related to the bibliographic entries - */ - public TexParserResult parse(String entryKey, List texFiles) { texParserResult.addFiles(texFiles); List referencedFiles = new ArrayList<>(); for (Path file : texFiles) { + if (Files.notExists(file)) { + LOGGER.error(String.format("File does not exist: %s", file)); + continue; + } + try (LineNumberReader lineNumberReader = new LineNumberReader(Files.newBufferedReader(file))) { for (String line = lineNumberReader.readLine(); line != null; line = lineNumberReader.readLine()) { // Skip comments and blank lines. if (line.isEmpty() || line.charAt(0) == '%') { continue; } - // Skip the citation matching if the line does not contain the given entry to speed up the parsing. - if (entryKey == null || line.contains(entryKey)) { - matchCitation(entryKey, file, lineNumberReader.getLineNumber(), line); - } + matchCitation(file, lineNumberReader.getLineNumber(), line); matchNestedFile(file, texFiles, referencedFiles, line); } } catch (ClosedChannelException e) { - LOGGER.info("Parsing has been interrupted"); - return texParserResult; - } catch (IOException e) { - LOGGER.error("Error opening a TEX file", e); + LOGGER.error("Parsing has been interrupted"); + return null; + } catch (IOException | UncheckedIOException e) { + LOGGER.error(String.format("Error searching files: %s", e.getMessage())); } } // Parse all files referenced by TEX files, recursively. if (!referencedFiles.isEmpty()) { - parse(entryKey, referencedFiles); + parse(referencedFiles); } return texParserResult; @@ -116,12 +108,11 @@ public TexParserResult parse(String entryKey, List texFiles) { /** * Find cites along a specific line and store them. */ - private void matchCitation(String entryKey, Path file, int lineNumber, String line) { + private void matchCitation(Path file, int lineNumber, String line) { Matcher citeMatch = CITE_PATTERN.matcher(line); while (citeMatch.find()) { Arrays.stream(citeMatch.group(CITE_GROUP).split(",")) - .filter(key -> entryKey == null || key.equals(entryKey)) .forEach(key -> texParserResult.addKey(key, file, lineNumber, citeMatch.start(), citeMatch.end(), line)); } } @@ -133,16 +124,12 @@ private void matchNestedFile(Path file, List texFiles, List referenc Matcher includeMatch = INCLUDE_PATTERN.matcher(line); while (includeMatch.find()) { - StringBuilder include = new StringBuilder(includeMatch.group(INCLUDE_GROUP)); - - if (!include.toString().endsWith(TEX_EXT)) { - include.append(TEX_EXT); - } + String include = includeMatch.group(INCLUDE_GROUP); - Path folder = file.getParent(); - Path inputFile = (folder == null) - ? Paths.get(include.toString()) - : folder.resolve(include.toString()); + Path inputFile = file.toAbsolutePath().getParent().resolve( + include.endsWith(TEX_EXT) + ? include + : String.format("%s%s", include, TEX_EXT)); if (!texFiles.contains(inputFile)) { referencedFiles.add(inputFile); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index d7c11a32df4..09a848eedb2 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2120,5 +2120,6 @@ Search\ citations\ for\ this\ entry\ in\ LaTeX\ files=Search citations for this Citations\ found=Citations found No\ citations\ found=No citations found No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=No LaTeX files containing this entry were found. -You\ can\ set\ the\ LaTeX\ file\ directory\ in\ the\ 'Library\ properties'\ dialog.=You can set the LaTeX file directory in the 'Library properties' dialog. Selected\ entry\ does\ not\ have\ an\ associated\ BibTeX\ key.=Selected entry does not have an associated BibTeX key. +Current\ search\ directory=Current search directory +Set\ LaTeX\ file\ directory=Set LaTeX file directory