Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit d99a8b4

Browse files
committed
Implment smart paste of links (surround selected text)
1 parent 7510412 commit d99a8b4

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 1.48.0 (Unreleased)
44

5+
- Implment smart paste of links (surround selected text)
56
- Use REditorSupport.r as ID for R extension
67

78
## 1.47.0 (Release on 13 October 2022)

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,12 @@
470470
"key": "ctrl+alt+r",
471471
"mac": "cmd+alt+r",
472472
"when": "editorTextFocus && editorLangId == quarto && !findInputFocussed && !replaceInputFocussed"
473+
},
474+
{
475+
"command": "quarto.paste",
476+
"key": "ctrl+v",
477+
"mac": "cmd+v",
478+
"when": "editorTextFocus && editorLangId == quarto && editorHasSelection && !findInputFocussed && !replaceInputFocussed"
473479
}
474480
],
475481
"menus": {

src/main.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { activateStatusBar } from "./providers/statusbar";
1919
import { walkthroughCommands } from "./providers/walkthrough";
2020
import { activateLuaTypes } from "./providers/lua-types";
2121
import { activateCreate } from "./providers/create/create";
22+
import { activatePaste } from "./providers/paste";
2223

2324
export async function activate(context: vscode.ExtensionContext) {
2425
// create markdown engine
@@ -76,6 +77,10 @@ export async function activate(context: vscode.ExtensionContext) {
7677
quartoCellExecuteCodeLensProvider(engine)
7778
);
7879

80+
// provide paste handling
81+
const pasteCommands = activatePaste();
82+
commands.push(...pasteCommands);
83+
7984
// activate providers common to browser/node
8085
activateCommon(context, engine, commands);
8186
}

src/providers/paste.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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

Comments
 (0)