Clean up and fix markdown url pasting (#198706)

Fixes #192568
This commit is contained in:
Matt Bierner 2023-11-20 14:37:00 -08:00 committed by GitHub
parent 032109e5a7
commit ff9fc384d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 181 additions and 173 deletions

View file

@ -507,7 +507,7 @@
"type": "string",
"scope": "resource",
"markdownDescription": "%configuration.markdown.editor.pasteUrlAsFormattedLink.enabled%",
"default":"never",
"default": "never",
"enum": [
"always",
"smart",
@ -734,6 +734,7 @@
"morphdom": "^2.6.1",
"picomatch": "^2.3.1",
"vscode-languageclient": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.3"
},
"devDependencies": {

View file

@ -3,18 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as vscode from 'vscode';
import { ITextDocument } from '../types/textDocument';
export class InMemoryDocument implements ITextDocument {
constructor(
public readonly uri: vscode.Uri,
private readonly _contents: string,
public readonly version = 0,
) { }
private readonly _doc: TextDocument;
getText(): string {
return this._contents;
public readonly uri: vscode.Uri;
public readonly version: number;
constructor(
uri: vscode.Uri,
contents: string,
version: number = 0,
) {
this.uri = uri;
this.version = version;
this._doc = TextDocument.create(this.uri.toString(), 'markdown', 0, contents);
}
getText(range?: vscode.Range): string {
return this._doc.getText(range);
}
}

View file

@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { Mime } from '../../util/mimes';
import { createEditAddingLinksForUriList, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink, validateLink } from './shared';
import { createEditAddingLinksForUriList, findValidUriInText, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared';
class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
@ -28,11 +28,16 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
const item = dataTransfer.get(Mime.textPlain);
const urlList = await item?.asString();
if (token.isCancellationRequested || !urlList || !validateLink(urlList).isValid) {
if (token.isCancellationRequested || !urlList) {
return;
}
const pasteEdit = createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
const uriText = findValidUriInText(urlList);
if (!uriText) {
return;
}
const pasteEdit = createEditAddingLinksForUriList(document, ranges, uriText, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
if (!pasteEdit) {
return;
}

View file

@ -6,6 +6,7 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as URI from 'vscode-uri';
import { ITextDocument } from '../../types/textDocument';
import { coalesce } from '../../util/arrays';
import { getDocumentDir } from '../../util/document';
import { mediaMimes } from '../../util/mimes';
@ -18,7 +19,7 @@ enum MediaKind {
Audio,
}
export const externalUriSchemes = [
const externalUriSchemes = [
'http',
'https',
'mailto',
@ -50,21 +51,6 @@ export const mediaFileExtensions = new Map<string, MediaKind>([
['wav', MediaKind.Audio],
]);
const smartPasteRegexes = [
{ regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link
{ regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block
{ regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block
{ regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block
{ regex: /`[^`]*`/g }, // In inline code
{ regex: /\$[^$]*\$/g }, // In inline math
];
export interface SkinnyTextDocument {
offsetAt(position: vscode.Position): number;
getText(range?: vscode.Range): string;
readonly uri: vscode.Uri;
}
export enum PasteUrlAsFormattedLink {
Always = 'always',
Smart = 'smart',
@ -76,16 +62,16 @@ export function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument)
}
export function createEditAddingLinksForUriList(
document: SkinnyTextDocument,
document: ITextDocument,
ranges: readonly vscode.Range[],
urlList: string,
isExternalLink: boolean,
useSmartPaste: boolean
useSmartPaste: boolean,
): { additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined {
if (ranges.length === 0) {
if (!ranges.length) {
return;
}
const edits: vscode.SnippetTextEdit[] = [];
let placeHolderValue: number = ranges.length;
let label: string = '';
@ -93,13 +79,8 @@ export function createEditAddingLinksForUriList(
let markdownLink: boolean = true;
for (const range of ranges) {
const selectedRange: vscode.Range = new vscode.Range(
new vscode.Position(range.start.line, document.offsetAt(range.start)),
new vscode.Position(range.end.line, document.offsetAt(range.end))
);
if (useSmartPaste) {
pasteAsMarkdownLink = checkSmartPaste(document, selectedRange, range);
pasteAsMarkdownLink = shouldSmartPaste(document, range);
markdownLink = pasteAsMarkdownLink; // FIX: this will only match the last range
}
@ -120,13 +101,47 @@ export function createEditAddingLinksForUriList(
return { additionalEdits, label, markdownLink };
}
export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vscode.Range, range: vscode.Range): boolean {
if (selectedRange.isEmpty || /^[\s\n]*$/.test(document.getText(range)) || validateLink(document.getText(range)).isValid) {
export function findValidUriInText(text: string): string | undefined {
const trimmedUrlList = text.trim();
// Uri must consist of a single sequence of characters without spaces
if (!/^\S+$/.test(trimmedUrlList)) {
return;
}
let uri: vscode.Uri;
try {
uri = vscode.Uri.parse(trimmedUrlList);
} catch {
// Could not parse
return;
}
if (!externalUriSchemes.includes(uri.scheme.toLowerCase()) || uri.authority.length <= 1) {
return;
}
return trimmedUrlList;
}
const smartPasteRegexes = [
{ regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link
{ regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block
{ regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block
{ regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block
{ regex: /`[^`]*`/g }, // In inline code
{ regex: /\$[^$]*\$/g }, // In inline math
];
export function shouldSmartPaste(document: ITextDocument, selectedRange: vscode.Range): boolean {
if (selectedRange.isEmpty || /^[\s\n]*$/.test(document.getText(selectedRange)) || findValidUriInText(document.getText(selectedRange))) {
return false;
}
if (/\[.*\]\(.*\)/.test(document.getText(range)) || /!\[.*\]\(.*\)/.test(document.getText(range))) {
if (/\[.*\]\(.*\)/.test(document.getText(selectedRange)) || /!\[.*\]\(.*\)/.test(document.getText(selectedRange))) {
return false;
}
for (const regex of smartPasteRegexes) {
const matches = [...document.getText().matchAll(regex.regex)];
for (const match of matches) {
@ -138,29 +153,14 @@ export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vsc
}
}
}
return true;
}
export function validateLink(urlList: string): { isValid: boolean; cleanedUrlList: string } {
let isValid = false;
let uri = undefined;
const trimmedUrlList = urlList?.trim(); //remove leading and trailing whitespace and new lines
try {
uri = vscode.Uri.parse(trimmedUrlList);
} catch (error) {
return { isValid: false, cleanedUrlList: urlList };
}
const splitUrlList = trimmedUrlList.split(' ').filter(item => item !== ''); //split on spaces and remove empty strings
if (uri) {
isValid = splitUrlList.length === 1 && !splitUrlList[0].includes('\n') && externalUriSchemes.includes(vscode.Uri.parse(splitUrlList[0]).scheme) && !!vscode.Uri.parse(splitUrlList[0]).authority;
}
return { isValid, cleanedUrlList: splitUrlList[0] };
}
export function tryGetUriListSnippet(document: SkinnyTextDocument, urlList: String, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): { snippet: vscode.SnippetString; label: string } | undefined {
const entries = coalesce(urlList.split(/\r?\n/g).map(resource => {
export function tryGetUriListSnippet(document: ITextDocument, urlList: String, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): { snippet: vscode.SnippetString; label: string } | undefined {
const entries = coalesce(urlList.split(/\r?\n/g).map(line => {
try {
return { uri: vscode.Uri.parse(resource), str: resource };
return { uri: vscode.Uri.parse(line), str: line };
} catch {
// Uri parse failure
return undefined;
@ -197,7 +197,7 @@ export function appendToLinkSnippet(
}
export function createUriListSnippet(
document: SkinnyTextDocument,
document: ITextDocument,
uris: ReadonlyArray<{
readonly uri: vscode.Uri;
readonly str?: string;
@ -412,4 +412,3 @@ function needsBracketLink(mdPath: string) {
return nestingCount > 0;
}

View file

@ -2,92 +2,101 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as assert from 'assert';
import 'mocha';
import { SkinnyTextDocument, checkSmartPaste, createEditAddingLinksForUriList, appendToLinkSnippet, validateLink } from '../languageFeatures/copyFiles/shared';
import * as vscode from 'vscode';
import { InMemoryDocument } from '../client/inMemoryDocument';
import { appendToLinkSnippet, createEditAddingLinksForUriList, findValidUriInText, shouldSmartPaste } from '../languageFeatures/copyFiles/shared';
suite('createEditAddingLinksForUriList', () => {
test('Markdown Link Pasting should occur for a valid link (end to end)', async () => {
// createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet
const skinnyDocument: SkinnyTextDocument = {
uri: vscode.Uri.parse('file:///path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return 'hello world!'; },
};
const result = createEditAddingLinksForUriList(skinnyDocument, [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true);
const result = createEditAddingLinksForUriList(
new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true);
// need to check the actual result -> snippet value
assert.strictEqual(result?.label, 'Insert Markdown Link');
});
suite('validateLink', () => {
test('Markdown pasting should occur for a valid link.', () => {
const isLink = validateLink('https://www.microsoft.com/').isValid;
assert.strictEqual(isLink, true);
test('Markdown pasting should occur for a valid link', () => {
assert.strictEqual(
findValidUriInText('https://www.microsoft.com/'),
'https://www.microsoft.com/');
});
test('Markdown pasting should occur for a valid link preceded by a new line.', () => {
const isLink = validateLink('\r\nhttps://www.microsoft.com/').isValid;
assert.strictEqual(isLink, true);
test('Markdown pasting should occur for a valid link preceded by a new line', () => {
assert.strictEqual(
findValidUriInText('\r\nhttps://www.microsoft.com/'),
'https://www.microsoft.com/');
});
test('Markdown pasting should occur for a valid link followed by a new line.', () => {
const isLink = validateLink('https://www.microsoft.com/\r\n').isValid;
assert.strictEqual(isLink, true);
test('Markdown pasting should occur for a valid link followed by a new line', () => {
assert.strictEqual(
findValidUriInText('https://www.microsoft.com/\r\n'),
'https://www.microsoft.com/');
});
test('Markdown pasting should not occur for a valid hostname and invalid protool.', () => {
const isLink = validateLink('invalid:www.microsoft.com').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for a valid hostname and invalid protool', () => {
assert.strictEqual(
findValidUriInText('invalid:www.microsoft.com'),
undefined);
});
test('Markdown pasting should not occur for plain text.', () => {
const isLink = validateLink('hello world!').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for plain text', () => {
assert.strictEqual(
findValidUriInText('hello world!'),
undefined);
});
test('Markdown pasting should not occur for plain text including a colon.', () => {
const isLink = validateLink('hello: world!').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for plain text including a colon', () => {
assert.strictEqual(
findValidUriInText('hello: world!'),
undefined);
});
test('Markdown pasting should not occur for plain text including a slashes.', () => {
const isLink = validateLink('helloworld!').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for plain text including a slashes', () => {
assert.strictEqual(
findValidUriInText('helloworld!'),
undefined);
});
test('Markdown pasting should not occur for a link followed by text.', () => {
const isLink = validateLink('https://www.microsoft.com/ hello world!').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for a link followed by text', () => {
assert.strictEqual(
findValidUriInText('https://www.microsoft.com/ hello world!'),
undefined);
});
test('Markdown pasting should occur for a link preceded or followed by spaces.', () => {
const isLink = validateLink(' https://www.microsoft.com/ ').isValid;
assert.strictEqual(isLink, true);
test('Markdown pasting should occur for a link preceded or followed by spaces', () => {
assert.strictEqual(
findValidUriInText(' https://www.microsoft.com/ '),
'https://www.microsoft.com/');
});
test('Markdown pasting should not occur for a link with an invalid scheme.', () => {
const isLink = validateLink('hello:www.microsoft.com').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for a link with an invalid scheme', () => {
assert.strictEqual(
findValidUriInText('hello:www.microsoft.com'),
undefined);
});
test('Markdown pasting should not occur for multiple links being pasted.', () => {
const isLink = validateLink('https://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for multiple links being pasted', () => {
assert.strictEqual(
findValidUriInText('https://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/'),
undefined);
});
test('Markdown pasting should not occur for multiple links with spaces being pasted.', () => {
const isLink = validateLink('https://www.microsoft.com/ \r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\n hello \r\nhttps://www.microsoft.com/').isValid;
assert.strictEqual(isLink, false);
test('Markdown pasting should not occur for multiple links with spaces being pasted', () => {
assert.strictEqual(
findValidUriInText('https://www.microsoft.com/ \r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\n hello \r\nhttps://www.microsoft.com/'),
undefined);
});
test('Markdown pasting should not occur for just a valid uri scheme', () => {
const isLink = validateLink('https://').isValid;
assert.strictEqual(isLink, false);
assert.strictEqual(
findValidUriInText('https://'),
undefined);
});
});
@ -132,107 +141,86 @@ suite('createEditAddingLinksForUriList', () => {
suite('checkSmartPaste', () => {
const skinnyDocument: SkinnyTextDocument = {
uri: vscode.Uri.file('/path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return 'hello world!'; },
};
test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => {
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 12), new vscode.Range(0, 0, 0, 12));
assert.strictEqual(pasteAsMarkdownLink, true);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('hello world'), new vscode.Range(0, 0, 0, 12)),
true);
});
test('Should evaluate pasteAsMarkdownLink as false for a valid selected link', () => {
skinnyDocument.getText = function () { return 'https://www.microsoft.com'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 25), new vscode.Range(0, 0, 0, 25));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('https://www.microsoft.com'), new vscode.Range(0, 0, 0, 25)),
false);
});
test('Should evaluate pasteAsMarkdownLink as false for a valid selected link with trailing whitespace', () => {
skinnyDocument.getText = function () { return ' https://www.microsoft.com '; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 30), new vscode.Range(0, 0, 0, 30));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc(' https://www.microsoft.com '), new vscode.Range(0, 0, 0, 30)),
false);
});
test('Should evaluate pasteAsMarkdownLink as true for a link pasted in square brackets', () => {
skinnyDocument.getText = function () { return '[abc]'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 1, 0, 4), new vscode.Range(0, 1, 0, 4));
assert.strictEqual(pasteAsMarkdownLink, true);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('[abc]'), new vscode.Range(0, 1, 0, 4)),
true);
});
test('Should evaluate pasteAsMarkdownLink as false for no selection', () => {
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 0), new vscode.Range(0, 0, 0, 0));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('xyz'), new vscode.Range(0, 0, 0, 0)),
false);
});
test('Should evaluate pasteAsMarkdownLink as false for selected whitespace and new lines', () => {
skinnyDocument.getText = function () { return ' \r\n\r\n'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 0, 0, 7), new vscode.Range(0, 0, 0, 7));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc(' \r\n\r\n'), new vscode.Range(0, 0, 0, 7)),
false);
});
test('Should evaluate pasteAsMarkdownLink as false for pasting within a backtick code block', () => {
skinnyDocument.getText = function () { return '```\r\n\r\n```'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('```\r\n\r\n```'), new vscode.Range(0, 5, 0, 5)),
false);
});
test('Should evaluate pasteAsMarkdownLink as false for pasting within a tilde code block', () => {
skinnyDocument.getText = function () { return '~~~\r\n\r\n~~~'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('~~~\r\n\r\n~~~'), new vscode.Range(0, 5, 0, 5)),
false);
});
test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => {
skinnyDocument.getText = function () { return '$$$\r\n\r\n$$$'; };
const pasteAsMarkdownLink = checkSmartPaste(skinnyDocument, new vscode.Range(0, 5, 0, 5), new vscode.Range(0, 5, 0, 5));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('$$$\r\n\r\n$$$'), new vscode.Range(0, 5, 0, 5)),
false);
});
const linkSkinnyDoc: SkinnyTextDocument = {
uri: vscode.Uri.file('/path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return '[a](bcdef)'; },
};
test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => {
const pasteAsMarkdownLink = checkSmartPaste(linkSkinnyDoc, new vscode.Range(0, 4, 0, 6), new vscode.Range(0, 4, 0, 6));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('[a](bcdef)'), new vscode.Range(0, 4, 0, 6)),
false);
});
const imageLinkSkinnyDoc: SkinnyTextDocument = {
uri: vscode.Uri.file('/path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return '![a](bcdef)'; },
};
test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => {
const pasteAsMarkdownLink = checkSmartPaste(imageLinkSkinnyDoc, new vscode.Range(0, 5, 0, 10), new vscode.Range(0, 5, 0, 10));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('![a](bcdef)'), new vscode.Range(0, 5, 0, 10)),
false);
});
const inlineCodeSkinnyCode: SkinnyTextDocument = {
uri: vscode.Uri.file('/path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return '``'; },
};
test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => {
const pasteAsMarkdownLink = checkSmartPaste(inlineCodeSkinnyCode, new vscode.Range(0, 1, 0, 1), new vscode.Range(0, 1, 0, 1));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('``'), new vscode.Range(0, 1, 0, 1)),
false);
});
const inlineMathSkinnyDoc: SkinnyTextDocument = {
uri: vscode.Uri.file('/path/to/your/file'),
offsetAt: function () { return 0; },
getText: function () { return '$$'; },
};
test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => {
const pasteAsMarkdownLink = checkSmartPaste(inlineMathSkinnyDoc, new vscode.Range(0, 1, 0, 1), new vscode.Range(0, 1, 0, 1));
assert.strictEqual(pasteAsMarkdownLink, false);
assert.strictEqual(
shouldSmartPaste(makeTestDoc('$$'), new vscode.Range(0, 1, 0, 1)),
false);
});
});
});
function makeTestDoc(contents: string) {
return new InMemoryDocument(vscode.Uri.file('test.md'), contents);
}

View file

@ -12,6 +12,6 @@ export interface ITextDocument {
readonly uri: vscode.Uri;
readonly version: number;
getText(): string;
getText(range?: vscode.Range): string;
}

View file

@ -295,6 +295,11 @@ vscode-languageserver-protocol@3.17.2:
vscode-jsonrpc "8.0.2"
vscode-languageserver-types "3.17.2"
vscode-languageserver-textdocument@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf"
integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==
vscode-languageserver-textdocument@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c"