|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) RStudio, PBC. All rights reserved. |
| 3 | + * Copyright (c) 2017 张宇 |
| 4 | + * Licensed under the MIT License. See LICENSE in the project root for license information. |
| 5 | + *--------------------------------------------------------------------------------------------*/ |
| 6 | + |
| 7 | +import { window, commands, env } from "vscode"; |
| 8 | + |
| 9 | +import { Command } from "../core/command"; |
| 10 | + |
| 11 | +export function activatePaste() { |
| 12 | + return [new QuartoPasteCommand()]; |
| 13 | +} |
| 14 | + |
| 15 | +export class QuartoPasteCommand implements Command { |
| 16 | + constructor() {} |
| 17 | + private static readonly id = "quarto.paste"; |
| 18 | + public readonly id = QuartoPasteCommand.id; |
| 19 | + |
| 20 | + async execute() { |
| 21 | + const editor = window.activeTextEditor!; |
| 22 | + const selection = editor.selection; |
| 23 | + if ( |
| 24 | + selection.isSingleLine && |
| 25 | + !isSingleLink(editor.document.getText(selection)) |
| 26 | + ) { |
| 27 | + const text = await env.clipboard.readText(); |
| 28 | + if (isSingleLink(text)) { |
| 29 | + return commands.executeCommand("editor.action.insertSnippet", { |
| 30 | + snippet: `[$TM_SELECTED_TEXT$0](${text})`, |
| 31 | + }); |
| 32 | + } |
| 33 | + } |
| 34 | + return commands.executeCommand("editor.action.clipboardPasteAction"); |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +/** |
| 39 | + * Checks if the string is a link. This code ported from django's |
| 40 | + * [URLValidator](https://github.com/django/django/blob/2.2b1/django/core/validators.py#L74) |
| 41 | + * with some simplifiations. |
| 42 | + */ |
| 43 | + |
| 44 | +function isSingleLink(text: string): boolean { |
| 45 | + return singleLinkRegex.test(text); |
| 46 | +} |
| 47 | +const singleLinkRegex: RegExp = createLinkRegex(); |
| 48 | + |
| 49 | +function createLinkRegex(): RegExp { |
| 50 | + // unicode letters range(must not be a raw string) |
| 51 | + const ul = "\\u00a1-\\uffff"; |
| 52 | + // IP patterns |
| 53 | + const ipv4_re = |
| 54 | + "(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}"; |
| 55 | + const ipv6_re = "\\[[0-9a-f:\\.]+\\]"; // simple regex (in django it is validated additionally) |
| 56 | + |
| 57 | + // Host patterns |
| 58 | + const hostname_re = |
| 59 | + "[a-z" + ul + "0-9](?:[a-z" + ul + "0-9-]{0,61}[a-z" + ul + "0-9])?"; |
| 60 | + // Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1 |
| 61 | + const domain_re = "(?:\\.(?!-)[a-z" + ul + "0-9-]{1,63}(?<!-))*"; |
| 62 | + |
| 63 | + const tld_re = |
| 64 | + "" + |
| 65 | + "\\." + // dot |
| 66 | + "(?!-)" + // can't start with a dash |
| 67 | + "(?:[a-z" + |
| 68 | + ul + |
| 69 | + "-]{2,63}" + // domain label |
| 70 | + "|xn--[a-z0-9]{1,59})" + // or punycode label |
| 71 | + "(?<!-)" + // can't end with a dash |
| 72 | + "\\.?"; // may have a trailing dot |
| 73 | + const host_re = "(" + hostname_re + domain_re + tld_re + "|localhost)"; |
| 74 | + const pattern = |
| 75 | + "" + |
| 76 | + "^(?:[a-z0-9\\.\\-\\+]*)://" + // scheme is not validated (in django it is validated additionally) |
| 77 | + "(?:[^\\s:@/]+(?::[^\\s:@/]*)?@)?" + // user: pass authentication |
| 78 | + "(?:" + |
| 79 | + ipv4_re + |
| 80 | + "|" + |
| 81 | + ipv6_re + |
| 82 | + "|" + |
| 83 | + host_re + |
| 84 | + ")" + |
| 85 | + "(?::\\d{2,5})?" + // port |
| 86 | + "(?:[/?#][^\\s]*)?" + // resource path |
| 87 | + "$"; // end of string |
| 88 | + return new RegExp(pattern, "i"); |
| 89 | +} |
0 commit comments