From e8aa6b7915e3bc2d8c4e3f1995c3d7cf5eef76ed Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Mon, 8 Apr 2024 12:09:03 +0200 Subject: [PATCH] bells and whistles and documentation for util::Clipboard; this adds insight into different mimetypes that are supported, as well as the ability to paste from very specific mimetypes --- src/org/rascalmpl/library/util/Clipboard.java | 47 +++++++++++++- src/org/rascalmpl/library/util/Clipboard.rsc | 62 ++++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/library/util/Clipboard.java b/src/org/rascalmpl/library/util/Clipboard.java index e06c877ca70..b69c75182cd 100644 --- a/src/org/rascalmpl/library/util/Clipboard.java +++ b/src/org/rascalmpl/library/util/Clipboard.java @@ -4,7 +4,14 @@ import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; +import java.io.Reader; +import java.util.Arrays; + import org.rascalmpl.debug.IRascalMonitor; +import org.rascalmpl.exceptions.RuntimeExceptionFactory; +import org.rascalmpl.library.Prelude; + +import io.usethesource.vallang.ISet; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValueFactory; @@ -26,8 +33,10 @@ public void copy(IString arg) { public IString paste() { try { - if (cp.isDataFlavorAvailable(DataFlavor.stringFlavor)) { - return vf.string(cp.getData(DataFlavor.stringFlavor).toString()); + DataFlavor flavor = DataFlavor.getTextPlainUnicodeFlavor(); + + try (Reader data = flavor.getReaderForText(cp.getContents(null))) { + return vf.string(Prelude.consumeInputStream(data)); } } catch (UnsupportedFlavorException | IOException e) { @@ -36,4 +45,38 @@ public IString paste() { return vf.string(""); } + + public IString paste(IString mimetype) { + try { + DataFlavor flavor = new DataFlavor(mimetype.getValue()); + + try (Reader data = flavor.getReaderForText(cp.getContents(null))) { + return vf.string(Prelude.consumeInputStream(data)); + } + } + catch (ClassNotFoundException e) { + throw RuntimeExceptionFactory.illegalArgument(vf.string("Unsupported clipboard mimetype: " + mimetype)); + } + catch (UnsupportedFlavorException e) { + throw RuntimeExceptionFactory.illegalArgument(vf.string("Unsupported clipboard mimetype: " + e.getMessage())); + } + catch (IOException e) { + throw RuntimeExceptionFactory.io(e.getMessage()); + } + } + + public ISet availableTextMimetypes() { + return Arrays.stream(cp.getAvailableDataFlavors()) + .filter(flavor -> flavor.isFlavorTextType()) + .map(flavor -> vf.tuple(vf.string(flavor.getHumanPresentableName()), vf.string(flavor.getMimeType()))) + .collect(vf.setWriter()); + } + + public ISet availableTextMimetypesFor(IString shortMimetype) { + return Arrays.stream(cp.getAvailableDataFlavors()) + .filter(flavor -> flavor.isFlavorTextType()) + .filter(flavor -> flavor.isMimeTypeEqual(shortMimetype.getValue())) + .map(flavor -> vf.tuple(vf.string(flavor.getHumanPresentableName()), vf.string(flavor.getMimeType()))) + .collect(vf.setWriter()); + } } diff --git a/src/org/rascalmpl/library/util/Clipboard.rsc b/src/org/rascalmpl/library/util/Clipboard.rsc index 74420960d93..f7c9ed20d86 100644 --- a/src/org/rascalmpl/library/util/Clipboard.rsc +++ b/src/org/rascalmpl/library/util/Clipboard.rsc @@ -1,6 +1,8 @@ @synopsis{Programmatic access to the native system clipboard (copy and paste), with some handy (de)-escaping features.} module util::Clipboard +import Exception; + @javaClass{org.rascalmpl.library.util.Clipboard} @synopsis{If there is textual content in the current clipboard, then return it as a string.} @description{ @@ -12,8 +14,13 @@ then paste always returns the empty string. } @pitfalls{ * the first time paste or copy are called the UI toolkit must boot up; it can take a few seconds. +* paste provides the content of the current clipboard, where other processes can always put new content int. +The `paste` function is thread-safe, it will provide the contents of the clipboard as it was at a certain +time, but it does not have to be the latest version. } @benefits{ +* past never throws exceptions, it just returns "" if something fails and prints an exception. If you +need a less robust version look at `past(str mimetype)` below. * copy/paste allow for interesting and useful user interactions, especially while experimenting on the REPL. * paste encodes and escapes all kinds of wild characters automatically. } @@ -31,9 +38,62 @@ then copy always has no effect. } @pitfalls{ * the first time paste or copy are called the UI toolkit must boot up; it can take a few seconds. +* if another application copies something to the clipboard, or content can be overwritten. There +is always a race for the clipboard, but this function is thread-safe. Right after its execution the +content of the clipboard is guaranteed to contain `content`. } @benefits{ * copy/paste allow for interesting and useful user interactions, especially while experimenting on the REPL. * copy transfers the pure content of the string, no quotes or escapes. } -java void copy(str content); \ No newline at end of file +java void copy(str content); + +@javaClass{org.rascalmpl.library.util.Clipboard} +@synopsis{Lists the available mimetypes on the current clipboard, restricted to the ones we can serialize to string.} +@description{ +The clipboard can contain many kinds of textual data. With this query you retrieve all the supported mimetypes that we can +safely and correctly serialize to a string. + +Most of the mimetypes contain the exact charset encoding parameter, such that we do not have to worry about that here. +Using `paste(str mimetype)` those details will be taken care off. However if you call `paste` with an unavailable +encoding, there will be an appropriate exception. + +If this function returns `{}` then there is nothing on the clipboard that can be serialized to a string. +} +@pitfalls{ +* after calling this function, the user may have selected another content for the clipboard. There is always a race +for the clipboard. This function is thread-friendly, however and will not crash but just provide outdated information. +} +java rel[str humanReadable, str fullMimetype] availableTextMimetypes(); + +@javaClass{org.rascalmpl.library.util.Clipboard} +@synopsis{Lists the available mimetypes on the current clipboard, restricted to the ones we can serialize to string and that start with `shortMimetype`.} +@description{ +The clipboard can contain many kinds of textual data. With this query you retrieve all the exact supported mimetypes +for, for example, `text/html`. The function will fill in the specific charset and implementation class parameters of +the mimetype, for all supported formats. + +Most of the mimetypes contain the exact charset encoding parameter, such that we do not have to worry about that here. +Using `paste(str mimetype)` those details will be taken care off. However if you call `paste` with an unavailable +encoding, there will be an appropriate exception. + +If this function returns `{}` then there is nothing on the clipboard that can be serialized to a string and matches +the `shortMimetype`. +} +@pitfalls{ +* after calling this function, the user may have selected another content for the clipboard. There is always a race +for the clipboard. This function is thread-friendly, however and will not crash but just provide outdated information. +} +java rel[str humanReadable, str fullMimetype] availableTextMimetypesFor(str shortMimetype); + +@javaClass{org.rascalmpl.library.util.Clipboard} +@synsopsis{Serializes the current contents of the clipboard that matches the given mimetype to a string} +@description{ +This only works for ((availableTextMimetypes)), otherwise an exception is thrown. + +This function behaves as `paste()`, but it can serialize all kinds of other data, as long as the internal +data flavors for the given mimetypes support textual serialization. In principle these are the mimetypes +that list a charset parameter, but there are also some heuristics that enlarge the set a bit. The supported +mimetypes are always listed in ((availableTextMimetypes)). +} +java str paste(str mimetype) throws IO, IllegalArgument;