From 8690860d4a7ac8fe8d9227ac62dcc35ed72dce27 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Sun, 8 Dec 2024 15:33:32 +0100 Subject: [PATCH] Pasting a URL from the clipboard wraps the selected text as a link (#1738) --- CHANGELOG.adoc | 1 + .../AsciiDocPasteLinkProvider.java | 123 ++++++++++++++++++ .../asciidoc/intellij/psi/AsciiDocUtil.java | 2 + src/main/resources/META-INF/plugin.xml | 1 + 4 files changed, 127 insertions(+) create mode 100644 src/main/java/org/asciidoc/intellij/pasteProvider/AsciiDocPasteLinkProvider.java diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 408054112..06e18352b 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -13,6 +13,7 @@ This document provides a high-level view of the changes introduced by release. - Adding an intent to add the option to disable validation on individual code blocks (#830) - Be more restrictive when highlighting missing images (#1727) - Fix the intention to add block IDs to recognize the current section (#1681) +- Pasting a URL from the clipboard wraps the selected text as a link (#1738) === 0.43.3 diff --git a/src/main/java/org/asciidoc/intellij/pasteProvider/AsciiDocPasteLinkProvider.java b/src/main/java/org/asciidoc/intellij/pasteProvider/AsciiDocPasteLinkProvider.java new file mode 100644 index 000000000..649101d50 --- /dev/null +++ b/src/main/java/org/asciidoc/intellij/pasteProvider/AsciiDocPasteLinkProvider.java @@ -0,0 +1,123 @@ +package org.asciidoc.intellij.pasteProvider; + +import com.intellij.ide.PasteProvider; +import com.intellij.openapi.actionSystem.ActionUpdateThread; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.actions.PasteAction; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import org.asciidoc.intellij.actions.asciidoc.DocumentWriteAction; +import org.asciidoc.intellij.file.AsciiDocFileType; +import org.asciidoc.intellij.psi.AsciiDocUtil; +import org.jetbrains.annotations.NotNull; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Objects; + +public class AsciiDocPasteLinkProvider implements PasteProvider { + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + + @Override + public void performPaste(@NotNull DataContext dataContext) { + final Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (editor == null) { + return; + } + DocumentWriteAction.run(editor.getProject(), () -> { + Document document = editor.getDocument(); + Caret currentCaret = editor.getCaretModel().getCurrentCaret(); + if (currentCaret.getSelectionRange().getLength() == 0) { + return; + } + Transferable produce = Objects.requireNonNull(dataContext.getData(PasteAction.TRANSFERABLE_PROVIDER)).produce(); + if (produce.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + String url = produce.getTransferData(DataFlavor.stringFlavor).toString(); + SelectionModel selectionModel = editor.getSelectionModel(); + if (AsciiDocUtil.URL_PREFIX_PATTERN.matcher(url).find() || AsciiDocUtil.EMAIL_PATTERN.matcher(url).matches()) { + int start = selectionModel.getSelectionStart(); + int end = selectionModel.getSelectionEnd(); + String oldText = document.getText(TextRange.create(start, end)); + String newText = oldText; + if (oldText.contains("]")) { + newText = newText.replaceAll("\\\\]", "\\\\\\\\]"); + newText = newText.replaceAll("]", "\\\\]"); + } + String prefix = url + "["; + if (url.contains("[")) { + prefix = "link:++" + url + "++["; + } else if (AsciiDocUtil.EMAIL_PATTERN.matcher(url).matches()) { + prefix = "link:mailto:" + url + "["; + } + if (!oldText.equals(newText)) { + document.replaceString(start, end, newText); + } + document.insertString(start, prefix); + document.insertString(start + prefix.length() + newText.length(), "]"); + // If the cursor was at the beginning of the selected text, it is now at the beginning of the URL. + // Move it to the beginning of the text again. + if (editor.getCaretModel().getCurrentCaret().getOffset() < start + prefix.length()) { + editor.getCaretModel().getCurrentCaret().moveToOffset(start + prefix.length()); + } + } + } catch (IOException | UnsupportedFlavorException ignored) { + // ignored + } + } + }, "Paste link"); + } + + @Override + public boolean isPasteEnabled(@NotNull DataContext dataContext) { + if (!isPastePossible(dataContext)) { + return false; + } + final Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (editor == null) { + return false; + } + Caret currentCaret = editor.getCaretModel().getCurrentCaret(); + if (currentCaret.getSelectionRange().getLength() == 0) { + return false; + } + Transferable produce = Objects.requireNonNull(dataContext.getData(PasteAction.TRANSFERABLE_PROVIDER)).produce(); + if (produce.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + String data = produce.getTransferData(DataFlavor.stringFlavor).toString(); + if (AsciiDocUtil.URL_PREFIX_PATTERN.matcher(data).find() || AsciiDocUtil.EMAIL_PATTERN.matcher(data).matches()) { + return true; + } + } catch (IOException | UnsupportedFlavorException e) { + return false; + } + } + return false; + } + + @Override + public boolean isPastePossible(@NotNull DataContext dataContext) { + final Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (editor == null) { + return false; + } + final PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext); + if (file == null) { + return false; + } + if (file.getFileType() != AsciiDocFileType.INSTANCE) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/asciidoc/intellij/psi/AsciiDocUtil.java b/src/main/java/org/asciidoc/intellij/psi/AsciiDocUtil.java index 33575c652..cb871480d 100644 --- a/src/main/java/org/asciidoc/intellij/psi/AsciiDocUtil.java +++ b/src/main/java/org/asciidoc/intellij/psi/AsciiDocUtil.java @@ -1322,6 +1322,8 @@ public static VirtualFile findAntoraModuleDir(PsiElement element) { public static final Pattern URL_PREFIX_PATTERN = Pattern.compile("^((https?|file|ftp|irc)://|mailto:)"); + public static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$"); + public static final Pattern URL_PREFIX_PATTERN_WITHOUT_FILE = Pattern.compile("^((https?|ftp|irc)://|mailto:)"); // can include attributes diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 28d5ff45f..dd3b7d19e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -199,6 +199,7 @@ +