commit f92f15dbd22e76c26686dbc9324d4a27f61f3938 Author: MrLetsplay2003 Date: Sun Nov 26 19:38:46 2023 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..144a04c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.settings/ +bin/ +.classpath diff --git a/.project b/.project new file mode 100644 index 0000000..577d111 --- /dev/null +++ b/.project @@ -0,0 +1,28 @@ + + + ShareClient + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..f206159 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: ShareClient +Bundle-SymbolicName: ShareClient;singleton:=true +Bundle-Version: 1.0.0 +Import-Package: javax.inject;version="[1.0.0,2.0.0)" +Bundle-Activator: me.mrletsplay.shareclient.Activator +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.ui.editors;bundle-version="3.17.0", + org.eclipse.text, + Java-WebSocket;bundle-version="1.5.4", + wrapped.me.mrletsplay.ShareClient-Core;bundle-version="1.0.0" +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Automatic-Module-Name: ShareClient +Bundle-ActivationPolicy: lazy diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..08adec3 --- /dev/null +++ b/build.properties @@ -0,0 +1,6 @@ +source.. = src/main/java/ +output.. = bin/ +bin.includes = plugin.xml,\ + META-INF/,\ + .,\ + icons/ diff --git a/contexts.xml b/contexts.xml new file mode 100644 index 0000000..f1513f5 --- /dev/null +++ b/contexts.xml @@ -0,0 +1,13 @@ + + + + This is the context help for the sample view with a table viewer. It was generated by a PDE template. + + + + + + + + + diff --git a/icons/sample.png b/icons/sample.png new file mode 100644 index 0000000..02c4b79 Binary files /dev/null and b/icons/sample.png differ diff --git a/icons/sample@2x.png b/icons/sample@2x.png new file mode 100644 index 0000000..c1224d1 Binary files /dev/null and b/icons/sample@2x.png differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..066680c --- /dev/null +++ b/plugin.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/me/mrletsplay/shareclient/Activator.java b/src/main/java/me/mrletsplay/shareclient/Activator.java new file mode 100644 index 0000000..1a04c98 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/Activator.java @@ -0,0 +1,47 @@ +package me.mrletsplay.shareclient; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "ShareClient"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + + System.out.println("STARTING"); +// new ShareWSClient(URI.create("ws://localhost:5473")).connect(); + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} diff --git a/src/main/java/me/mrletsplay/shareclient/handlers/SampleHandler.java b/src/main/java/me/mrletsplay/shareclient/handlers/SampleHandler.java new file mode 100644 index 0000000..b809884 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/handlers/SampleHandler.java @@ -0,0 +1,105 @@ +package me.mrletsplay.shareclient.handlers; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.texteditor.ITextEditor; + +import me.mrletsplay.shareclientcore.connection.RemoteConnection; +import me.mrletsplay.shareclientcore.connection.WebSocketConnection; +import me.mrletsplay.shareclientcore.document.DocumentListener; +import me.mrletsplay.shareclientcore.document.SharedDocument; + +public class SampleHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); + MessageDialog.openInformation( + window.getShell(), + "ShareClient", + "Hello, Eclipse world"); + + IEditorPart editor = window.getActivePage().getActiveEditor(); + if(!(editor instanceof ITextEditor)) return null; + + ITextEditor textEditor = (ITextEditor) editor; + + IDocument eclipseDocument = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); + + RemoteConnection con = new WebSocketConnection(URI.create("ws://localhost:5473")); + try { + con.connect(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + AtomicBoolean ignoreChanges = new AtomicBoolean(false); + SharedDocument doc = new SharedDocument(con); + doc.localInsert(0, eclipseDocument.get()); + + doc.addListener(new DocumentListener() { + + @Override + public void onInsert(int index, char character) { + Display.getDefault().asyncExec(() -> { + try { + ignoreChanges.set(true); + eclipseDocument.replace(index, 0, String.valueOf(character)); + ignoreChanges.set(false); + } catch (BadLocationException e) { + e.printStackTrace(); + } + }); + + } + + @Override + public void onDelete(int index) { + Display.getDefault().asyncExec(() -> { + try { + ignoreChanges.set(true); + eclipseDocument.replace(index, 1, ""); + ignoreChanges.set(false); + } catch (BadLocationException e) { + e.printStackTrace(); + } + }); + } + }); + + eclipseDocument.addDocumentListener(new IDocumentListener() { + + @Override + public void documentChanged(DocumentEvent event) { + if(ignoreChanges.get()) return; // TODO: not very ideal + + if(event.getLength() > 0) { + doc.localDelete(event.getOffset(), event.getLength()); + } + + doc.localInsert(event.getOffset(), event.getText()); + } + + @Override + public void documentAboutToBeChanged(DocumentEvent event) { + + } + }); + + return null; + } +} diff --git a/src/main/java/me/mrletsplay/shareclient/views/ShareView.java b/src/main/java/me/mrletsplay/shareclient/views/ShareView.java new file mode 100644 index 0000000..34d2eb4 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/views/ShareView.java @@ -0,0 +1,186 @@ +package me.mrletsplay.shareclient.views; + + +import javax.inject.Inject; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.ViewPart; + + +/** + * This sample class demonstrates how to plug-in a new + * workbench view. The view shows data obtained from the + * model. The sample creates a dummy model on the fly, + * but a real implementation would connect to the model + * available either in this or another plug-in (e.g. the workspace). + * The view is connected to the model using a content provider. + *

+ * The view uses a label provider to define how model + * objects should be presented in the view. Each + * view can present the same model objects using + * different labels and icons, if needed. Alternatively, + * a single label provider can be shared between views + * in order to ensure that objects of the same type are + * presented in the same way everywhere. + *

+ */ + +public class ShareView extends ViewPart { + + /** + * The ID of the view as specified by the extension. + */ + public static final String ID = "me.mrletsplay.shareclient.views.ShareView"; + + @Inject IWorkbench workbench; + + private TableViewer viewer; + private Action action1; + private Action action2; + private Action doubleClickAction; + + + class ViewLabelProvider extends LabelProvider implements ITableLabelProvider { + @Override + public String getColumnText(Object obj, int index) { + return getText(obj); + } + @Override + public Image getColumnImage(Object obj, int index) { + return getImage(obj); + } + @Override + public Image getImage(Object obj) { + return workbench.getSharedImages().getImage(ISharedImages.IMG_OBJ_ELEMENT); + } + } + + @Override + public void createPartControl(Composite parent) { + viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + + viewer.setContentProvider(ArrayContentProvider.getInstance()); + viewer.setInput(new String[] { "One", "Two", "Three" }); + viewer.setLabelProvider(new ViewLabelProvider()); + + // Create the help context id for the viewer's control + workbench.getHelpSystem().setHelp(viewer.getControl(), "ShareClient.viewer"); + getSite().setSelectionProvider(viewer); + makeActions(); + hookContextMenu(); + hookDoubleClickAction(); + contributeToActionBars(); + } + + private void hookContextMenu() { + MenuManager menuMgr = new MenuManager("#PopupMenu"); + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() { + @Override + public void menuAboutToShow(IMenuManager manager) { + ShareView.this.fillContextMenu(manager); + } + }); + Menu menu = menuMgr.createContextMenu(viewer.getControl()); + viewer.getControl().setMenu(menu); + getSite().registerContextMenu(menuMgr, viewer); + } + + private void contributeToActionBars() { + IActionBars bars = getViewSite().getActionBars(); + fillLocalPullDown(bars.getMenuManager()); + fillLocalToolBar(bars.getToolBarManager()); + } + + private void fillLocalPullDown(IMenuManager manager) { + manager.add(action1); + manager.add(new Separator()); + manager.add(action2); + } + + private void fillContextMenu(IMenuManager manager) { + manager.add(action1); + manager.add(action2); + // Other plug-ins can contribute there actions here + manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); + } + + private void fillLocalToolBar(IToolBarManager manager) { + manager.add(action1); + manager.add(action2); + } + + private void makeActions() { + action1 = new Action() { + @Override + public void run() { + showMessage("Action 1 executed"); + } + }; + action1.setText("Action 1"); + action1.setToolTipText("Action 1 tooltip"); + action1.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). + getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); + + action2 = new Action() { + @Override + public void run() { + showMessage("Action 2 executed"); + } + }; + action2.setText("Action 2"); + action2.setToolTipText("Action 2 tooltip"); + action2.setImageDescriptor(workbench.getSharedImages(). + getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); + doubleClickAction = new Action() { + @Override + public void run() { + IStructuredSelection selection = viewer.getStructuredSelection(); + Object obj = selection.getFirstElement(); + showMessage("Double-click detected on "+obj.toString()); + } + }; + } + + private void hookDoubleClickAction() { + viewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + doubleClickAction.run(); + } + }); + } + private void showMessage(String message) { + MessageDialog.openInformation( + viewer.getControl().getShell(), + "Sample View", + message); + } + + @Override + public void setFocus() { + viewer.getControl().setFocus(); + } +} diff --git a/src/test/java/me/mrletsplay/shareclientcore/DecimalTest.java b/src/test/java/me/mrletsplay/shareclientcore/DecimalTest.java new file mode 100644 index 0000000..99bf09f --- /dev/null +++ b/src/test/java/me/mrletsplay/shareclientcore/DecimalTest.java @@ -0,0 +1,133 @@ +package me.mrletsplay.shareclientcore; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import me.mrletsplay.shareclientcore.document.Identifier; +import me.mrletsplay.shareclientcore.document.Util; + +public class DecimalTest { + + @Test + public void testSimpleSubtraction() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 0), new Identifier(3, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(1, 0), new Identifier(3, 0) }; + assertArrayEquals(new int[] { 0, 1, 0 }, Util.subtract(a, b, 0)); + } + + @Test + public void testCarrySubtraction() { + Identifier[] a = { new Identifier(2, 0), new Identifier(0, 0), new Identifier(4, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(0, 0), new Identifier(5, 0) }; + assertArrayEquals(new int[] { 0, Util.BASE - 1, Util.BASE - 1 }, Util.subtract(a, b, 0)); + } + + @Test + public void testOffsetSubtraction() { + Identifier[] a = { new Identifier(1, 0), new Identifier(0, 0), new Identifier(5, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(0, 0), new Identifier(4, 0) }; + assertArrayEquals(new int[] { 0, 1 }, Util.subtract(a, b, 1)); + } + + @Test + public void testReversedInputFails() { + Identifier[] a = { new Identifier(1, 0), new Identifier(1, 0), new Identifier(3, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(2, 0), new Identifier(3, 0) }; + assertThrows(RuntimeException.class, () -> Util.subtract(a, b, 0)); + } + + @Test + public void testSimpleAdd1() { + int[] a = { 0, 0 }; + Util.add1AtIndex(a, 1); + assertArrayEquals(new int[] { 0, 1 }, a); + } + + @Test + public void testCarryAdd1() { + int[] a = { 0, Util.BASE - 1 }; + Util.add1AtIndex(a, 1); + assertArrayEquals(new int[] { 1, 0 }, a); + } + + @Test + public void testCarryAdd1_2() { + int[] a = { 0, Util.BASE - 1, Util.BASE - 1 }; + Util.add1AtIndex(a, 2); + assertArrayEquals(new int[] { 1, 0, 0 }, a); + } + + @Test + public void testCarryAdd1_3() { + int[] a = { 0, Util.BASE - 1, Util.BASE - 1 }; + Util.add1AtIndex(a, 1); + assertArrayEquals(new int[] { 1, 0, Util.BASE - 1 }, a); + } + + @Test + public void testIncrement_1() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(3, 0) }; + assertArrayEquals(new int[] { 1, 2, 1 }, Util.getIncremented(a, b, 1)); + } + + @Test + public void testIncrement_2() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(2, 0), new Identifier(Util.BASE - 1, 0) }; + assertArrayEquals(new int[] { 1, 2, 0, 1 }, Util.getIncremented(a, b, 1)); + } + + @Test + public void testCarryIncrement() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 0), new Identifier(Util.BASE - 1, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(4, 0) }; + assertArrayEquals(new int[] { 1, 3, 1 }, Util.getIncremented(a, b, 1)); + } + + @Test + public void testConstructPosition_1() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 0), new Identifier(Util.BASE - 1, 0) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(4, 0) }; + int[] newIdent = Util.getIncremented(a, b, 1); + Identifier[] expected = { new Identifier(1, 0), new Identifier(3, 1), new Identifier(1, 1) }; + assertArrayEquals(expected, Util.constructPosition(newIdent, a, b, 1)); + } + + @Test + public void testConstructPosition_2() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 2), new Identifier(2, 2) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(4, 1), new Identifier(3, 1) }; + int[] newIdent = Util.getIncremented(a, b, 1); + Identifier[] expected = { new Identifier(1, 0), new Identifier(2, 2), new Identifier(3, 3) }; + assertArrayEquals(expected, Util.constructPosition(newIdent, a, b, 3)); + } + + @Test + public void testGeneratePosition_1() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 1) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(3, 2) }; + Identifier[] newIdent = Util.generatePositionBetween(a, b, 3); + Identifier[] expected = { new Identifier(1, 0), new Identifier(2, 1), new Identifier(1, 3) }; + assertArrayEquals(expected, newIdent); + } + + @Test + public void testGeneratePosition_2() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 1) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(2, 2) }; + Identifier[] newIdent = Util.generatePositionBetween(a, b, 3); + Identifier[] expected = { new Identifier(1, 0), new Identifier(2, 1), new Identifier(1, 3) }; + assertArrayEquals(expected, newIdent); + } + + @Test + public void testGeneratePositionInvalidInputFails() { + Identifier[] a = { new Identifier(1, 0), new Identifier(2, 2) }; + Identifier[] b = { new Identifier(1, 0), new Identifier(2, 1) }; + assertThrows(IllegalArgumentException.class, () -> Util.generatePositionBetween(a, b, 3)); + } + +} diff --git a/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java b/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java new file mode 100644 index 0000000..6e35723 --- /dev/null +++ b/src/test/java/me/mrletsplay/shareclientcore/DocumentTest.java @@ -0,0 +1,49 @@ +package me.mrletsplay.shareclientcore; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import me.mrletsplay.shareclientcore.connection.DummyConnection; +import me.mrletsplay.shareclientcore.document.SharedDocument; + +public class DocumentTest { + + @Test + public void testLocalInsert() { + SharedDocument doc = new SharedDocument(new DummyConnection()); + doc.localInsert(0, "Hello"); + assertEquals("Hello", doc.getContents()); + doc.localInsert(5, " World"); + assertEquals("Hello World", doc.getContents()); + doc.localInsert(5, " Test"); + assertEquals("Hello Test World", doc.getContents()); + } + + @Test + public void testLocalInsertInvalidIndexFails() { + SharedDocument doc = new SharedDocument(new DummyConnection()); + doc.localInsert(0, "Hello"); + assertThrows(IllegalArgumentException.class, () -> doc.localInsert(-1, "Test")); + assertThrows(IllegalArgumentException.class, () -> doc.localInsert(6, "Test")); + } + + @Test + public void testLocalDelete() { + SharedDocument doc = new SharedDocument(new DummyConnection()); + doc.localInsert(0, "Hello World!"); + doc.localDelete(5, 6); + assertEquals("Hello!", doc.getContents()); + } + + @Test + public void testLocalDeleteInvalidIndexFails() { + SharedDocument doc = new SharedDocument(new DummyConnection()); + doc.localInsert(0, "Hello World!"); + assertThrows(IllegalArgumentException.class, () -> doc.localDelete(-1, 10)); + assertThrows(IllegalArgumentException.class, () -> doc.localDelete(12, 1)); + assertThrows(IllegalArgumentException.class, () -> doc.localDelete(0, 13)); + } + +}