diff --git a/CHANGELOG.md b/CHANGELOG.md index 172492abf17..38461b0d26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added the possibility to redownload files that had been present but are no longer in the specified location. [#10848](https://github.com/JabRef/jabref/issues/10848) - We added the citation key pattern `[camelN]`. Equivalent to the first N words of the `[camel]` pattern. - We added ability to export in CFF (Citation File Format) [#10661](https://github.com/JabRef/jabref/issues/10661). +- We added ability to push entries to TeXworks. [#3197](https://github.com/JabRef/jabref/issues/3197) - We added the ability to zoom in and out in the document viewer using Ctrl + Scroll. [#10964](https://github.com/JabRef/jabref/pull/10964) ### Changed diff --git a/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java b/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java index 3de3e190432..0c4e1b88d93 100644 --- a/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java +++ b/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java @@ -17,6 +17,7 @@ import org.jabref.preferences.PreferencesService; import org.jabref.preferences.PushToApplicationPreferences; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +58,11 @@ public Action getAction() { @Override public void pushEntries(BibDatabaseContext database, List entries, String keyString) { + pushEntries(database, entries, keyString, new ProcessBuilder()); + } + + @VisibleForTesting + protected void pushEntries(BibDatabaseContext database, List entries, String keyString, ProcessBuilder processBuilder) { couldNotPush = false; couldNotCall = false; notDefined = false; @@ -77,7 +83,7 @@ public void pushEntries(BibDatabaseContext database, List entries, Str LOGGER.error("Commandline does not contain enough parameters to \"push to application\""); return; } - ProcessBuilder processBuilder = new ProcessBuilder( + processBuilder.command( "open", "-a", commands[0], @@ -88,7 +94,7 @@ public void pushEntries(BibDatabaseContext database, List entries, Str ); processBuilder.start(); } else { - ProcessBuilder processBuilder = new ProcessBuilder(getCommandLine(keyString)); + processBuilder.command(getCommandLine(keyString)); processBuilder.start(); } } catch (IOException excep) { @@ -122,7 +128,9 @@ public boolean requiresCitationKeys() { } /** - * Function to get the command to be executed for pushing keys to be cited + * Constructs the command line arguments for pushing citations to the application. + * The method formats the citation key and prefixes/suffixes as per user preferences + * before invoking the application with the command to insert text. * * @param keyString String containing the Bibtex keys to be pushed to the application * @return String array with the command to call and its arguments diff --git a/src/main/java/org/jabref/gui/push/PushToApplication.java b/src/main/java/org/jabref/gui/push/PushToApplication.java index b7d5223e83d..defe018f0c1 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplication.java +++ b/src/main/java/org/jabref/gui/push/PushToApplication.java @@ -13,10 +13,24 @@ */ public interface PushToApplication { + /** + * Gets the display name for the push operation. This name is used + * in the GUI to represent the push action to the user. + * + * @return The display name for the push operation. + */ String getDisplayName(); + /** + * Gets a tooltip for the push operation. + */ String getTooltip(); + /** + * Gets the icon associated with the application. + * + * @return The icon for the application. + */ JabRefIcon getApplicationIcon(); /** diff --git a/src/main/java/org/jabref/gui/push/PushToApplications.java b/src/main/java/org/jabref/gui/push/PushToApplications.java index 91d087f84fd..da229ac2c59 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplications.java +++ b/src/main/java/org/jabref/gui/push/PushToApplications.java @@ -13,6 +13,7 @@ public class PushToApplications { public static final String LYX = "LyX/Kile"; public static final String TEXMAKER = "Texmaker"; public static final String TEXSTUDIO = "TeXstudio"; + public static final String TEXWORKS = "TeXworks"; public static final String VIM = "Vim"; public static final String WIN_EDT = "WinEdt"; public static final String SUBLIME_TEXT = "Sublime Text"; @@ -34,6 +35,7 @@ public static List getAllApplications(DialogService dialogSer new PushToSublimeText(dialogService, preferencesService), new PushToTexmaker(dialogService, preferencesService), new PushToTeXstudio(dialogService, preferencesService), + new PushToTeXworks(dialogService, preferencesService), new PushToVim(dialogService, preferencesService), new PushToWinEdt(dialogService, preferencesService), new PushToTexShop(dialogService, preferencesService))); diff --git a/src/main/java/org/jabref/gui/push/PushToTeXworks.java b/src/main/java/org/jabref/gui/push/PushToTeXworks.java new file mode 100644 index 00000000000..fbc052a64c7 --- /dev/null +++ b/src/main/java/org/jabref/gui/push/PushToTeXworks.java @@ -0,0 +1,37 @@ +package org.jabref.gui.push; + +import org.jabref.gui.DialogService; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.preferences.PreferencesService; + +public class PushToTeXworks extends AbstractPushToApplication { + + public static final String NAME = PushToApplications.TEXWORKS; + + /** + * Constructs a new {@code PushToTeXworks} instance. + * + * @param dialogService The dialog service for displaying messages to the user. + * @param preferencesService The service for accessing user preferences. + */ + public PushToTeXworks(DialogService dialogService, PreferencesService preferencesService) { + super(dialogService, preferencesService); + } + + @Override + public String getDisplayName() { + return NAME; + } + + @Override + public JabRefIcon getApplicationIcon() { + // TODO: replace the placeholder icon with the real one. + return IconTheme.JabRefIcons.APPLICATION_GENERIC; + } + + @Override + protected String[] getCommandLine(String keyString) { + return new String[] {commandPath, "--insert-text", "%s%s%s".formatted(getCitePrefix(), keyString, getCiteSuffix())}; + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index fe106584d86..8b881ea8b15 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -150,6 +150,7 @@ public class JabRefPreferences implements PreferencesService { public static final String PUSH_EMACS_ADDITIONAL_PARAMETERS = "emacsParameters"; public static final String PUSH_LYXPIPE = "lyxpipe"; public static final String PUSH_TEXSTUDIO_PATH = "TeXstudioPath"; + public static final String PUSH_TEXWORKS_PATH = "TeXworksPath"; public static final String PUSH_WINEDT_PATH = "winEdtPath"; public static final String PUSH_TEXMAKER_PATH = "texmakerPath"; public static final String PUSH_VIM_SERVER = "vimServer"; @@ -543,6 +544,7 @@ private JabRefPreferences() { defaults.put(PUSH_TEXMAKER_PATH, OS.getNativeDesktop().detectProgramPath("texmaker", "Texmaker")); defaults.put(PUSH_WINEDT_PATH, OS.getNativeDesktop().detectProgramPath("WinEdt", "WinEdt Team\\WinEdt")); defaults.put(PUSH_TEXSTUDIO_PATH, OS.getNativeDesktop().detectProgramPath("texstudio", "TeXstudio")); + defaults.put(PUSH_TEXWORKS_PATH, OS.getNativeDesktop().detectProgramPath("texworks", "TeXworks")); defaults.put(PUSH_SUBLIME_TEXT_PATH, OS.getNativeDesktop().detectProgramPath("subl", "Sublime")); defaults.put(PUSH_LYXPIPE, USER_HOME + File.separator + ".lyx/lyxpipe"); defaults.put(PUSH_VIM, "vim"); @@ -1783,6 +1785,7 @@ public PushToApplicationPreferences getPushToApplicationPreferences() { applicationCommands.put(PushToApplications.LYX, get(PUSH_LYXPIPE)); applicationCommands.put(PushToApplications.TEXMAKER, get(PUSH_TEXMAKER_PATH)); applicationCommands.put(PushToApplications.TEXSTUDIO, get(PUSH_TEXSTUDIO_PATH)); + applicationCommands.put(PushToApplications.TEXWORKS, get(PUSH_TEXWORKS_PATH)); applicationCommands.put(PushToApplications.VIM, get(PUSH_VIM)); applicationCommands.put(PushToApplications.WIN_EDT, get(PUSH_WINEDT_PATH)); applicationCommands.put(PushToApplications.SUBLIME_TEXT, get(PUSH_SUBLIME_TEXT_PATH)); @@ -1812,6 +1815,8 @@ private void storePushToApplicationPath(Map commandPair) { put(PUSH_TEXMAKER_PATH, value); case PushToApplications.TEXSTUDIO -> put(PUSH_TEXSTUDIO_PATH, value); + case PushToApplications.TEXWORKS -> + put(PUSH_TEXWORKS_PATH, value); case PushToApplications.VIM -> put(PUSH_VIM, value); case PushToApplications.WIN_EDT -> diff --git a/src/test/java/org/jabref/gui/push/PushToTeXworksTest.java b/src/test/java/org/jabref/gui/push/PushToTeXworksTest.java new file mode 100644 index 00000000000..4dad0b67582 --- /dev/null +++ b/src/test/java/org/jabref/gui/push/PushToTeXworksTest.java @@ -0,0 +1,102 @@ +package org.jabref.gui.push; + +import java.util.Map; + +import javafx.beans.property.SimpleMapProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; + +import org.jabref.gui.DialogService; +import org.jabref.logic.push.CitationCommandString; +import org.jabref.preferences.ExternalApplicationsPreferences; +import org.jabref.preferences.PreferencesService; +import org.jabref.preferences.PushToApplicationPreferences; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PushToTeXworksTest { + + private static final String TEXWORKS_CLIENT_PATH = "/usr/bin/texworks"; + private static final String DISPLAY_NAME = "TeXworks"; + + private PushToTeXworks pushToTeXworks; + + @BeforeEach + public void setup() { + DialogService dialogService = mock(DialogService.class, Answers.RETURNS_DEEP_STUBS); + PreferencesService preferencesService = mock(PreferencesService.class); + PushToApplicationPreferences pushToApplicationPreferences = mock(PushToApplicationPreferences.class); + + // Mock the command path + Map commandPaths = Map.of(DISPLAY_NAME, TEXWORKS_CLIENT_PATH); + ObservableMap observableCommandPaths = FXCollections.observableMap(commandPaths); + when(pushToApplicationPreferences.getCommandPaths()).thenReturn(new SimpleMapProperty<>(observableCommandPaths)); + when(preferencesService.getPushToApplicationPreferences()).thenReturn(pushToApplicationPreferences); + + // Mock the return value for getCiteCommand() + ExternalApplicationsPreferences externalApplicationsPreferences = mock(ExternalApplicationsPreferences.class); + CitationCommandString mockCiteCommand = mock(CitationCommandString.class); + when(mockCiteCommand.prefix()).thenReturn(""); + when(mockCiteCommand.suffix()).thenReturn(""); + when(externalApplicationsPreferences.getCiteCommand()).thenReturn(mockCiteCommand); + when(preferencesService.getExternalApplicationsPreferences()).thenReturn(externalApplicationsPreferences); + + // Create a new instance of PushToTeXworks + pushToTeXworks = new PushToTeXworks(dialogService, preferencesService); + } + + /** + * To verify that the PushToTeXworks class correctly returns its designated display name. + * The display name is used to identify the application in the GUI. + */ + @Test + void displayName() { + assertEquals(DISPLAY_NAME, pushToTeXworks.getDisplayName()); + } + + /** + * To verify that the PushToTeXworks class correctly returns the command line for TeXworks. + * The command line is used to execute the application from the command line. + */ + @Test + void getCommandLine() { + String keyString = "TestKey"; + String[] expectedCommand = new String[] {null, "--insert-text", keyString}; // commandPath is only set in pushEntries + + String[] actualCommand = pushToTeXworks.getCommandLine(keyString); + + assertArrayEquals(expectedCommand, actualCommand); + } + + /** + * Check for the actual command and path with path is run. + */ + @Test + void pushEntries() { + ProcessBuilder processBuilder = mock(ProcessBuilder.class); + + String testKey = "TestKey"; + String[] expectedCommand = new String[] {TEXWORKS_CLIENT_PATH, "--insert-text", testKey}; + + pushToTeXworks.pushEntries(null, null, testKey, processBuilder); + + verify(processBuilder).command(expectedCommand); + } + + /** + * To verify that the PushToTeXworks class correctly returns the tooltip for TeXworks. + * The tooltip is used to display a short description of the application in the GUI. + */ + @Test + void getTooltip() { + assertEquals("Push entries to external application (TeXworks)", pushToTeXworks.getTooltip()); + } +}