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());
+ }
+}