implement simple in memory clipboard

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/InMemoryClipboardTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/InMemoryClipboardTest.java
new file mode 100644
index 0000000..c5a7e90
--- /dev/null
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/InMemoryClipboardTest.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
+import org.eclipse.vex.core.internal.io.UniversalTestDocument;
+import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.INode;
+import org.junit.Before;
+import org.junit.Test;
+
+public class InMemoryClipboardTest {
+
+	private UniversalTestDocument document;
+	private DocumentEditor editor;
+	private InMemoryClipboard clipboard;
+
+	@Before
+	public void setUp() throws Exception {
+		document = new UniversalTestDocument(1);
+		editor = new DocumentEditor(new FakeCursor(document.getDocument()));
+		editor.setDocument(document.getDocument());
+		clipboard = new InMemoryClipboard();
+	}
+
+	@Test
+	public void givenFirstParagraphSelected_cutSelection_shouldRemoveFirstParagraphFromDocument() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+
+		select(firstParagraph);
+		clipboard.cutSelection(editor);
+
+		assertNotSame(firstParagraph, section.children().first());
+		assertNull(firstParagraph.getDocument());
+	}
+
+	@Test
+	public void givenFirstParagraphSelected_cutAndPasteAfterSecondParagraph_shouldInsertFirstParagraphAfterSecondParagraph() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+		final INode secondParagraph = section.children().last();
+		final String firstParagraphContent = firstParagraph.getText();
+
+		select(firstParagraph);
+		clipboard.cutSelection(editor);
+		editor.moveTo(secondParagraph.getEndPosition().moveBy(1));
+		clipboard.paste(editor);
+
+		assertEquals(firstParagraphContent, section.children().last().getText());
+		assertEquals(2, section.children().count());
+	}
+
+	@Test
+	public void givenFirstParagraphSelected_copyAndPasteAfterSecondParagraph_shouldInsertFirstParagraphAgainAfterSecondParagraph() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+		final INode secondParagraph = section.children().last();
+		final String firstParagraphContent = firstParagraph.getText();
+
+		select(firstParagraph);
+		clipboard.copySelection(editor);
+		editor.moveTo(secondParagraph.getEndPosition().moveBy(1));
+		clipboard.paste(editor);
+
+		assertEquals(firstParagraphContent, section.children().last().getText());
+		assertEquals(3, section.children().count());
+	}
+
+	@Test
+	public void givenNothingSelected_cutSelection_shouldNotChangeClipboardContent() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+		final INode secondParagraph = section.children().last();
+		final String firstParagraphContent = firstParagraph.getText();
+
+		select(firstParagraph);
+		clipboard.copySelection(editor);
+		editor.moveTo(secondParagraph.getEndPosition().moveBy(1));
+		clipboard.cutSelection(editor);
+		clipboard.paste(editor);
+
+		assertEquals(firstParagraphContent, section.children().last().getText());
+		assertEquals(3, section.children().count());
+	}
+
+	@Test
+	public void givenNothingSelected_copySelection_shouldNotChangeClipboardContent() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+		final INode secondParagraph = section.children().last();
+		final String firstParagraphContent = firstParagraph.getText();
+
+		select(firstParagraph);
+		clipboard.copySelection(editor);
+		editor.moveTo(secondParagraph.getEndPosition().moveBy(1));
+		clipboard.copySelection(editor);
+		clipboard.paste(editor);
+
+		assertEquals(firstParagraphContent, section.children().last().getText());
+		assertEquals(3, section.children().count());
+	}
+
+	@Test
+	public void givenFirstParagraphSelected_copyAndPasteTextIntoSecondParagraph_shouldInsertTextOfFirstParagraphIntoSecondParagraph() throws Exception {
+		final IElement section = document.getSection(0);
+		final INode firstParagraph = section.children().first();
+		final INode secondParagraph = section.children().last();
+		final String firstParagraphContent = firstParagraph.getText();
+
+		select(firstParagraph);
+		clipboard.copySelection(editor);
+		editor.moveTo(secondParagraph.getEndPosition());
+		clipboard.pasteText(editor);
+
+		assertEquals(firstParagraphContent, section.children().last().getText());
+		assertEquals(2, section.children().count());
+	}
+
+	private void select(final INode node) {
+		editor.moveTo(node.getStartPosition());
+		editor.moveBy(1, true);
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
index 7bd765f..a750425 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
@@ -78,6 +78,7 @@
 
 	private final ICursor cursor;
 	private final EditStack editStack;
+	private final IClipboard clipboard;
 
 	private IDocument document;
 	private IWhitespacePolicy whitespacePolicy;
@@ -93,6 +94,7 @@
 		this.whitespacePolicy = whitespacePolicy;
 
 		editStack = new EditStack();
+		clipboard = new InMemoryClipboard();
 	}
 
 	/*
@@ -230,38 +232,41 @@
 
 	@Override
 	public void cutSelection() {
-		// TODO Auto-generated method stub
-
+		if (isReadOnly()) {
+			throw new ReadOnlyException("Cannot cut selection, because the editor is read-only.");
+		}
+		clipboard.cutSelection(this);
 	}
 
 	@Override
 	public void copySelection() {
-		// TODO Auto-generated method stub
-
+		clipboard.copySelection(this);
 	}
 
 	@Override
 	public boolean canPaste() {
-		// TODO Auto-generated method stub
-		return false;
+		return clipboard.hasContent();
 	}
 
 	@Override
 	public void paste() throws DocumentValidationException {
-		// TODO Auto-generated method stub
-
+		if (isReadOnly()) {
+			throw new ReadOnlyException("Cannot paste, because the editor is read-only.");
+		}
+		clipboard.paste(this);
 	}
 
 	@Override
 	public boolean canPasteText() {
-		// TODO Auto-generated method stub
-		return false;
+		return clipboard.hasTextContent();
 	}
 
 	@Override
 	public void pasteText() throws DocumentValidationException {
-		// TODO Auto-generated method stub
-
+		if (isReadOnly()) {
+			throw new ReadOnlyException("Cannot paste text, because the editor is read-only.");
+		}
+		clipboard.pasteText(this);
 	}
 
 	/*
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IClipboard.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IClipboard.java
new file mode 100644
index 0000000..56f5574
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IClipboard.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.widget;
+
+import org.eclipse.vex.core.provisional.dom.DocumentValidationException;
+
+public interface IClipboard {
+
+	/**
+	 * Cuts the current selection to the clipboard.
+	 *
+	 * @param editor
+	 *            TODO
+	 */
+	void cutSelection(IDocumentEditor editor);
+
+	/**
+	 * Copy the current selection to the clipboard.
+	 *
+	 * @param editor
+	 *            TODO
+	 */
+	void copySelection(IDocumentEditor editor);
+
+	/**
+	 * Returns true if the clipboard has content that can be pasted. Used to enable/disable the paste action of a
+	 * containing application.
+	 */
+	boolean hasContent();
+
+	/**
+	 * Paste the current clipboard contents into the document at the current caret position.
+	 *
+	 * @param editor
+	 *            TODO
+	 */
+	void paste(IDocumentEditor editor) throws DocumentValidationException;
+
+	/**
+	 * Returns true if the clipboard has plain text content that can be pasted. Used to enable/disable the "paste text"
+	 * action of a containing application.
+	 */
+	boolean hasTextContent();
+
+	/**
+	 * Paste the current clipboard contents as plain text into the document at the current caret position.
+	 *
+	 * @param editor
+	 *            TODO
+	 */
+	void pasteText(IDocumentEditor editor) throws DocumentValidationException;
+
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/InMemoryClipboard.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/InMemoryClipboard.java
new file mode 100644
index 0000000..61badb0
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/InMemoryClipboard.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.widget;
+
+import org.eclipse.vex.core.provisional.dom.DocumentValidationException;
+import org.eclipse.vex.core.provisional.dom.IDocumentFragment;
+
+public class InMemoryClipboard implements IClipboard {
+
+	private IDocumentFragment content;
+
+	@Override
+	public void cutSelection(final IDocumentEditor editor) {
+		copySelection(editor);
+		editor.deleteSelection();
+	}
+
+	@Override
+	public void copySelection(final IDocumentEditor editor) {
+		if (!editor.hasSelection()) {
+			return;
+		}
+
+		content = editor.getSelectedFragment();
+	}
+
+	@Override
+	public boolean hasContent() {
+		if (content == null) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public void paste(final IDocumentEditor editor) throws DocumentValidationException {
+		if (!hasContent()) {
+			return;
+		}
+
+		editor.insertFragment(content);
+	}
+
+	@Override
+	public boolean hasTextContent() {
+		if (content == null) {
+			return false;
+		}
+		final String text = content.getText();
+		if (text == null || "".equals(text)) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public void pasteText(final IDocumentEditor editor) throws DocumentValidationException {
+		if (!hasTextContent()) {
+			return;
+		}
+
+		editor.insertText(content.getText());
+	}
+
+}