Merge vscode source through 1.62 release (#19981)

* Build breaks 1

* Build breaks

* Build breaks

* Build breaks

* More build breaks

* Build breaks (#2512)

* Runtime breaks

* Build breaks

* Fix dialog location break

* Update typescript

* Fix ASAR break issue

* Unit test breaks

* Update distro

* Fix breaks in ADO builds (#2513)

* Bump to node 16

* Fix hygiene errors

* Bump distro

* Remove reference to node type

* Delete vscode specific extension

* Bump to node 16 in CI yaml

* Skip integration tests in CI builds (while fixing)

* yarn.lock update

* Bump moment dependency in remote yarn

* Fix drop-down chevron style

* Bump to node 16

* Remove playwrite from ci.yaml

* Skip building build scripts in hygine check
This commit is contained in:
Karl Burtram
2022-07-11 14:09:32 -07:00
committed by GitHub
parent fa0fcef303
commit 26455e9113
1876 changed files with 72050 additions and 37997 deletions
@@ -51,7 +51,8 @@
".devcontainer.json"
],
"filenamePatterns": [
"**/.devcontainer/devcontainer.json"
"**/.devcontainer/devcontainer.json",
"**/User/snippets/*.json"
]
}
],
@@ -14,9 +14,19 @@
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer"
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -124,13 +124,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -263,7 +276,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -514,13 +527,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -653,7 +679,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -870,13 +896,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -1009,7 +1048,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -1200,13 +1239,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -1339,7 +1391,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -1495,13 +1547,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -1634,7 +1699,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -24,13 +24,26 @@
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"features": {
"type": "object",
"description": "Features to add to the dev container.",
"additionalProperties": true
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".",
"items": {
"type": "integer",
"maximum": 65535,
"minimum": 0
"oneOf": [
{
"type": "integer",
"maximum": 65535,
"minimum": 0
},
{
"type": "string",
"pattern": "^([a-z0-9\\-]+):(\\d{1,5})$"
}
]
}
},
"portsAttributes": {
@@ -78,7 +91,10 @@
},
"protocol": {
"type": "string",
"enum": ["http", "https"],
"enum": [
"http",
"https"
],
"description": "The protocol to use when forwarding this port."
}
},
@@ -140,7 +156,10 @@
},
"protocol": {
"type": "string",
"enum": ["http", "https"],
"enum": [
"http",
"https"
],
"description": "The protocol to use when forwarding this port."
}
},
@@ -156,7 +175,7 @@
},
"updateRemoteUserUID": {
"type": "boolean",
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default."
"description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder."
},
"remoteEnv": {
"type": "object",
@@ -83,7 +83,7 @@ export class SettingsDocument {
completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)")));
completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code")));
completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH")));
completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "a dirty indicator if the active editor is dirty")));
completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "an indicator for when the active editor has unsaved changes")));
completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values")));
return Promise.resolve(completions);
+1 -1
View File
@@ -6,7 +6,7 @@
"git": {
"name": "dotnet/csharp-tmLanguage",
"repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage",
"commitHash": "16612717ccd557383c0c821d7b6ae6662492ffde"
"commitHash": "5426265f1be3f8056a984b709fadf56b9ce4c400"
}
},
"license": "MIT",
@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/dotnet/csharp-tmLanguage/commit/16612717ccd557383c0c821d7b6ae6662492ffde",
"version": "https://github.com/dotnet/csharp-tmLanguage/commit/5426265f1be3f8056a984b709fadf56b9ce4c400",
"name": "C#",
"scopeName": "source.cs",
"patterns": [
+25 -2
View File
@@ -2128,6 +2128,7 @@
"deprecationMessage": "This setting is now deprecated, please use `github.gitAuthentication` instead."
},
"git.timeline.date": {
"type": "string",
"enum": [
"committed",
"authored"
@@ -2145,6 +2146,28 @@
"default": true,
"description": "%config.timeline.showAuthor%",
"scope": "window"
},
"git.showUnpublishedCommitsButton": {
"type": "string",
"enum": [
"always",
"whenEmpty",
"never"
],
"enumDescriptions": [
"%config.showUnpublishedCommitsButton.always%",
"%config.showUnpublishedCommitsButton.whenEmpty%",
"%config.showUnpublishedCommitsButton.never%"
],
"default": "whenEmpty",
"description": "%config.showUnpublishedCommitsButton%",
"scope": "resource"
},
"git.statusLimit": {
"type": "number",
"scope": "resource",
"default": 10000,
"description": "%config.statusLimit%"
}
}
},
@@ -2382,8 +2405,8 @@
"byline": "^5.0.0",
"file-type": "^7.2.0",
"iconv-lite-umd": "0.6.8",
"jschardet": "2.3.0",
"vscode-extension-telemetry": "0.2.8",
"jschardet": "3.0.0",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^4.0.0",
"vscode-uri": "^2.0.0",
"which": "^1.3.0"
+5
View File
@@ -185,6 +185,11 @@
"config.timeline.date.committed": "Use the committed date",
"config.timeline.date.authored": "Use the authored date",
"config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.",
"config.showUnpublishedCommitsButton": "Controls whether to show an action button to sync or publish, if there are unpublished commits.",
"config.showUnpublishedCommitsButton.always": "Always shows the action button, if there are unpublished commits.",
"config.showUnpublishedCommitsButton.whenEmpty": "Only shows the action button if there are no other changes and there are unpublished commits.",
"config.showUnpublishedCommitsButton.never": "Never shows the action button.",
"config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.",
"submenu.explorer": "Git",
"submenu.commit": "Commit",
"submenu.commit.amend": "Amend",
+10 -11
View File
@@ -694,27 +694,26 @@ export class CommandCenter {
viewColumn: ViewColumn.Active
};
let document;
try {
document = await workspace.openTextDocument(uri);
} catch (error) {
await commands.executeCommand('vscode.open', uri, {
...opts,
override: arg instanceof Resource && arg.type === Status.BOTH_MODIFIED ? false : undefined
});
await commands.executeCommand('vscode.open', uri, {
...opts,
override: arg instanceof Resource && arg.type === Status.BOTH_MODIFIED ? false : undefined
});
const document = window.activeTextEditor?.document;
// If the document doesn't match what we opened then don't attempt to select the range
if (document?.uri.toString() !== uri.toString()) {
continue;
}
// Check if active text editor has same path as other editor. we cannot compare via
// URI.toString() here because the schemas can be different. Instead we just go by path.
if (activeTextEditor && activeTextEditor.document.uri.path === uri.path) {
if (activeTextEditor && activeTextEditor.document.uri.path === uri.path && document) {
// preserve not only selection but also visible range
opts.selection = activeTextEditor.selection;
const previousVisibleRanges = activeTextEditor.visibleRanges;
const editor = await window.showTextDocument(document, opts);
editor.revealRange(previousVisibleRanges[0]);
} else {
await commands.executeCommand('vscode.open', uri, opts);
}
}
}
-7
View File
@@ -48,13 +48,6 @@ export class GitFileSystemProvider implements FileSystemProvider {
model.onDidChangeRepository(this.onDidChangeRepository, this),
model.onDidChangeOriginalResource(this.onDidChangeOriginalResource, this),
workspace.registerFileSystemProvider('git', this, { isReadonly: true, isCaseSensitive: true }),
workspace.registerResourceLabelFormatter({
scheme: 'git',
formatting: {
label: '${path} (git)',
separator: '/'
}
})
);
setInterval(() => this.cleanup(), FIVE_MINUTES);
+1 -1
View File
@@ -1842,7 +1842,7 @@ export class Repository {
const onStdoutData = (raw: string) => {
parser.update(raw);
if (parser.status.length > limit) {
if (limit !== 0 && parser.status.length > limit) {
child.removeListener('exit', onExit);
child.stdout!.removeListener('data', onStdoutData);
child.kill();
+61 -13
View File
@@ -842,7 +842,7 @@ export class Repository implements Disposable {
return this.repository.dotGit;
}
private isRepositoryHuge = false;
private isRepositoryHuge: false | { limit: number } = false;
private didWarnAboutLimit = false;
private resourceCommandResolver = new ResourceCommandResolver(this);
@@ -917,6 +917,8 @@ export class Repository implements Disposable {
|| e.affectsConfiguration('git.untrackedChanges', root)
|| e.affectsConfiguration('git.ignoreSubmodules', root)
|| e.affectsConfiguration('git.openDiffOnClick', root)
|| e.affectsConfiguration('git.rebaseWhenSync', root)
|| e.affectsConfiguration('git.showUnpublishedCommitsButton', root)
)(this.updateModelState, this, this.disposables);
const updateInputBoxVisibility = () => {
@@ -971,6 +973,14 @@ export class Repository implements Disposable {
}
validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
let tooManyChangesWarning: SourceControlInputBoxValidation | undefined;
if (this.isRepositoryHuge) {
tooManyChangesWarning = {
message: localize('tooManyChangesWarning', "Too many changes were detected. Only the first {0} changes will be shown below.", this.isRepositoryHuge.limit),
type: SourceControlInputBoxValidationType.Warning
};
}
if (this.rebaseCommit) {
if (this.rebaseCommit.message !== text) {
return {
@@ -984,7 +994,7 @@ export class Repository implements Disposable {
const setting = config.get<'always' | 'warn' | 'off'>('inputValidation');
if (setting === 'off') {
return;
return tooManyChangesWarning;
}
if (/^\s+$/.test(text)) {
@@ -1020,7 +1030,7 @@ export class Repository implements Disposable {
if (line.length <= threshold) {
if (setting !== 'always') {
return;
return tooManyChangesWarning;
}
return {
@@ -1790,12 +1800,16 @@ export class Repository implements Disposable {
const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
const ignoreSubmodules = scopedConfig.get<boolean>('ignoreSubmodules');
const { status, didHitLimit } = await this.repository.getStatus({ ignoreSubmodules });
const limit = scopedConfig.get<number>('statusLimit', 5000);
const { status, didHitLimit } = await this.repository.getStatus({ limit, ignoreSubmodules });
const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLimitWarning') === true;
const useIcons = !config.get<boolean>('decorations.enabled', true);
this.isRepositoryHuge = didHitLimit;
this.isRepositoryHuge = didHitLimit ? { limit } : false;
// Triggers or clears any validation warning
this._sourceControl.inputBox.validateInput = this._sourceControl.inputBox.validateInput;
if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) {
const knownHugeFolderPaths = await this.findKnownHugeFolderPathsToIgnore();
@@ -1808,18 +1822,21 @@ export class Repository implements Disposable {
const addKnown = localize('add known', "Would you like to add '{0}' to .gitignore?", folderName);
const yes = { title: localize('yes', "Yes") };
const no = { title: localize('no', "No") };
const result = await window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, neverAgain);
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
this.didWarnAboutLimit = true;
} else if (result === yes) {
const result = await window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, no, neverAgain);
if (result === yes) {
this.ignore([Uri.file(folderPath)]);
} else {
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
}
this.didWarnAboutLimit = true;
}
} else {
const result = await window.showWarningMessage(gitWarn, neverAgain);
const ok = { title: localize('ok', "OK") };
const result = await window.showWarningMessage(gitWarn, ok, neverAgain);
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
}
@@ -1906,6 +1923,37 @@ export class Repository implements Disposable {
return undefined;
});
let actionButton: SourceControl['actionButton'];
if (HEAD !== undefined) {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const showActionButton = config.get<string>('showUnpublishedCommitsButton', 'whenEmpty');
if (showActionButton === 'always' || (showActionButton === 'whenEmpty' && workingTree.length === 0 && index.length === 0 && untracked.length === 0 && merge.length === 0)) {
if (HEAD.name && HEAD.commit) {
if (HEAD.upstream) {
if (HEAD.ahead) {
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
actionButton = {
command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync',
title: localize('scm button sync title', ' Sync Changes $(sync){0}{1}', HEAD.behind ? `${HEAD.behind}$(arrow-down) ` : '', `${HEAD.ahead}$(arrow-up)`),
tooltip: this.syncTooltip,
arguments: [this._sourceControl],
};
}
} else {
actionButton = {
command: 'git.publish',
title: localize('scm button publish title', "$(cloud-upload) Publish Changes"),
tooltip: localize('scm button publish tooltip', "Publish Changes"),
arguments: [this._sourceControl],
};
}
}
}
}
this._sourceControl.actionButton = actionButton;
// set resource groups
this.mergeGroup.resourceStates = merge;
this.indexGroup.resourceStates = index;
+8 -8
View File
@@ -56,15 +56,15 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
jschardet@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.3.0.tgz#06e2636e16c8ada36feebbdc08aa34e6a9b3ff75"
integrity sha512-6I6xT7XN/7sBB7q8ObzKbmv5vN+blzLcboDE1BNEsEfmRXJValMxO6OIRT69ylPBRemS3rw6US+CMCar0OBc9g==
jschardet@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882"
integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^4.0.0:
version "4.0.0"
@@ -61,7 +61,7 @@
"dependencies": {
"node-fetch": "^2.6.7",
"uuid": "8.1.0",
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0",
"vscode-tas-client": "^0.1.42"
},
@@ -5,29 +5,12 @@
// keytar depends on a native module shipped in vscode, so this is
// how we load it
import type * as keytarType from 'keytar';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Log } from './logger';
const localize = nls.loadMessageBundle();
function getKeytar(): Keytar | undefined {
try {
return require('keytar');
} catch (err) {
console.log(err);
}
return undefined;
}
export type Keytar = {
getPassword: typeof keytarType['getPassword'];
setPassword: typeof keytarType['setPassword'];
deletePassword: typeof keytarType['deletePassword'];
};
export class Keychain {
constructor(
private readonly context: vscode.ExtensionContext,
@@ -72,25 +55,4 @@ export class Keychain {
return Promise.resolve(undefined);
}
}
async tryMigrate(): Promise<string | null | undefined> {
try {
const keytar = getKeytar();
if (!keytar) {
throw new Error('keytar unavailable');
}
const oldValue = await keytar.getPassword(`${vscode.env.uriScheme}-github.login`, 'account');
if (oldValue) {
this.Logger.trace('Attempting to migrate from keytar to secret store...');
await this.setToken(oldValue);
await keytar.deletePassword(`${vscode.env.uriScheme}-github.login`, 'account');
}
return oldValue;
} catch (_) {
// Ignore
return Promise.resolve(undefined);
}
}
}
@@ -117,7 +117,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
let sessionData: SessionData[];
try {
this._logger.info('Reading sessions from keychain...');
const storedSessions = await this._keychain.getToken() || await this._keychain.tryMigrate();
const storedSessions = await this._keychain.getToken();
if (!storedSessions) {
return [];
}
@@ -195,12 +195,13 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
scopes: JSON.stringify(scopes),
});
const token = await this._githubServer.login(scopes.join(' '));
const scopeString = scopes.join(' ');
const token = await this._githubServer.login(scopeString);
this.afterTokenLoad(token);
const session = await this.tokenToSession(token, scopes);
const sessions = await this._sessionsPromise;
const sessionIndex = sessions.findIndex(s => s.id === session.id);
const sessionIndex = sessions.findIndex(s => s.id === session.id || s.scopes.join(' ') === scopeString);
if (sessionIndex > -1) {
sessions.splice(sessionIndex, 1, session);
} else {
@@ -122,15 +122,13 @@ export class GitHubServer implements IGitHubServer {
// TODO@joaomoreno TODO@TylerLeonhardt
private async isNoCorsEnvironment(): Promise<boolean> {
const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`));
return (uri.scheme === 'https' && /^(vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
return (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
}
public async login(scopes: string): Promise<string> {
this._logger.info(`Logging in for the following scopes: ${scopes}`);
// TODO@joaomoreno TODO@TylerLeonhardt
const nocors = await this.isNoCorsEnvironment();
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate${nocors ? '?nocors=true' : ''}`));
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
if (this.isTestEnvironment(callbackUri)) {
const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true });
@@ -148,7 +146,7 @@ export class GitHubServer implements IGitHubServer {
return tokenScopes.includes(splitScopes);
});
})) {
throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
}
return token;
@@ -160,7 +158,7 @@ export class GitHubServer implements IGitHubServer {
const existingStates = this._pendingStates.get(scopes) || [];
this._pendingStates.set(scopes, [...existingStates, state]);
const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com${nocors ? '&nocors=true' : ''}`);
const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`);
await vscode.env.openExternal(uri);
// Register a single listener for the URI callback, in case the user starts the login process multiple times
@@ -208,34 +206,23 @@ export class GitHubServer implements IGitHubServer {
const url = `https://${AUTH_RELAY_SERVER}/token?code=${code}&state=${query.state}`;
this._logger.info('Exchanging code for token...');
// TODO@joao: remove
if (query.nocors) {
try {
const json: any = await vscode.commands.executeCommand('_workbench.fetchJSON', url, 'POST');
try {
const result = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json'
}
});
if (result.ok) {
const json = await result.json();
this._logger.info('Token exchange success!');
resolve(json.access_token);
} catch (err) {
reject(err);
}
} else {
try {
const result = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json'
}
});
if (result.ok) {
const json = await result.json();
this._logger.info('Token exchange success!');
resolve(json.access_token);
} else {
reject(result.statusText);
}
} catch (ex) {
reject(ex);
} else {
reject(result.statusText);
}
} catch (ex) {
reject(ex);
}
};
@@ -291,6 +278,9 @@ export class GitHubServer implements IGitHubServer {
}
public async sendAdditionalTelemetryInfo(token: string): Promise<void> {
if (!vscode.env.isTelemetryEnabled) {
return;
}
const nocors = await this.isNoCorsEnvironment();
if (nocors) {
@@ -394,7 +384,7 @@ export class GitHubEnterpriseServer implements IGitHubServer {
return tokenScopes.includes(splitScopes);
});
})) {
throw new Error(`The provided token is does not match the requested scopes: ${scopes}`);
throw new Error(`The provided token does not match the requested scopes: ${scopes}`);
}
return token;
+4 -4
View File
@@ -99,10 +99,10 @@ uuid@8.1.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"
+1 -1
View File
@@ -82,7 +82,7 @@ async function handlePushError(repository: Repository, remote: Remote, refspec:
await repository.push('origin', localName, true);
return [octokit, ghRepository];
return [octokit, ghRepository] as const;
});
// yield
+1 -1
View File
@@ -59,7 +59,7 @@
];
const settings = getSettings();
const isMac = settings.isMac;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const vscode = acquireVsCodeApi();
+1 -1
View File
@@ -80,7 +80,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"repository": {
+1 -10
View File
@@ -93,6 +93,7 @@ class Preview extends Disposable {
webviewEditor.webview.options = {
enableScripts: true,
enableForms: false,
localResourceRoots: [
resourceRoot,
extensionRoot,
@@ -205,7 +206,6 @@ class Preview extends Disposable {
private async getWebviewContents(): Promise<string> {
const version = Date.now().toString();
const settings = {
isMac: isMac(),
src: await this.getResourcePath(this.webviewEditor, this.resource, version),
};
@@ -261,15 +261,6 @@ class Preview extends Disposable {
}
}
declare const process: undefined | { readonly platform: string };
function isMac(): boolean {
if (typeof process === 'undefined') {
return false;
}
return process.platform === 'darwin';
}
function escapeAttribute(value: string | vscode.Uri): string {
return value.toString().replace(/"/g, '&quot;');
}
+4 -4
View File
@@ -2,10 +2,10 @@
# yarn lockfile v1
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"
@@ -1,103 +1,31 @@
{
// Note that this file should stay in sync with 'typescript-language-basics/language-configuration.json'
"comments": {
"lineComment": "//",
"blockComment": [
"/*",
"*/"
]
"blockComment": [ "/*", "*/" ]
},
"brackets": [
[
"${",
"}"
],
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
["${", "}"],
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{
"open": "{",
"close": "}"
},
{
"open": "[",
"close": "]"
},
{
"open": "(",
"close": ")"
},
{
"open": "'",
"close": "'",
"notIn": [
"string",
"comment"
]
},
{
"open": "\"",
"close": "\"",
"notIn": [
"string"
]
},
{
"open": "`",
"close": "`",
"notIn": [
"string",
"comment"
]
},
{
"open": "/**",
"close": " */",
"notIn": [
"string"
]
}
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
{ "open": "/**", "close": " */", "notIn": ["string"] }
],
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"'",
"'"
],
[
"\"",
"\""
],
[
"`",
"`"
],
[
"<",
">"
]
["{", "}"],
["[", "]"],
["(", ")"],
["'", "'"],
["\"", "\""],
["`", "`"],
["<", ">"]
],
"autoCloseBefore": ";:.,=}])>` \n\t",
"folding": {
@@ -105,89 +33,5 @@
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
},
"wordPattern": {
"pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>/\\?\\s]+)",
},
"indentationRules": {
"decreaseIndentPattern": {
"pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]].*$"
},
"increaseIndentPattern": {
"pattern": "^((?!//).)*(\\{([^}\"'`/]*|(\\t|[ ])*//.*)|\\([^)\"'`/]*|\\[[^\\]\"'`/]*)$"
},
// e.g. * ...| or */| or *-----*/|
"unIndentedLinePattern": {
"pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$"
}
},
"onEnterRules": [
{
// e.g. /** | */
"beforeText": {
"pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$"
},
"afterText": {
"pattern": "^\\s*\\*/$"
},
"action": {
"indent": "indentOutdent",
"appendText": " * "
}
},
{
// e.g. /** ...|
"beforeText": {
"pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$"
},
"action": {
"indent": "none",
"appendText": " * "
}
},
{
// e.g. * ...|
"beforeText": {
"pattern": "^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$"
},
"previousLineText": {
"pattern": "(?=^(\\s*(/\\*\\*|\\*)).*)(?=(?!(\\s*\\*/)))"
},
"action": {
"indent": "none",
"appendText": "* "
}
},
{
// e.g. */|
"beforeText": {
"pattern": "^(\\t|[ ])*[ ]\\*/\\s*$"
},
"action": {
"indent": "none",
"removeText": 1
},
},
{
// e.g. *-----*/|
"beforeText": {
"pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$"
},
"action": {
"indent": "none",
"removeText": 1
},
},
{
"beforeText": {
"pattern": "^\\s*(\\bcase\\s.+:|\\bdefault:)$"
},
"afterText": {
"pattern": "^(?!\\s*(\\bcase\\b|\\bdefault\\b))"
},
"action": {
"indent": "indent"
}
}
]
}
}
@@ -37,6 +37,11 @@
")"
]
],
"colorizedBracketPairs": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{
"open": "{",
@@ -220,7 +220,9 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
*/
runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath });
}
return runtime.http.getContent(uriPath);
return runtime.http.getContent(uriPath).catch(e => {
return Promise.reject(new ResponseError(4, e.toString()));
});
} else {
return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)));
}
@@ -233,7 +235,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
}
return false;
};
const handleActiveEditorChange = (activeEditor?: TextEditor) => {
if (!activeEditor) {
return;
@@ -64,12 +64,19 @@ function getPackageInfo(context: ExtensionContext): IPackageInfo {
function getHTTPRequestService(): RequestService {
return {
getContent(uri: string, _encoding?: string) {
getContent(uri: string, _encoding?: string): Promise<string> {
const headers = { 'Accept-Encoding': 'gzip, deflate' };
return xhr({ url: uri, followRedirects: 5, headers }).then(response => {
return response.responseText;
}, (error: XHRResponse) => {
return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString());
let status = getErrorStatusDescription(error.status);
if (status && error.responseText) {
status = `${status}\n${error.responseText.substring(0, 200)}`;
}
if (!status) {
status = error.toString();
}
return Promise.reject(status);
});
}
};
@@ -6,7 +6,7 @@
import { Uri } from 'vscode';
export interface RequestService {
getContent(uri: string, encoding?: string): Thenable<string>;
getContent(uri: string, encoding?: string): Promise<string>;
}
export function getScheme(uri: string) {
@@ -135,7 +135,7 @@
},
"dependencies": {
"request-light": "^0.5.4",
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-languageclient": "^7.0.0",
"vscode-nls": "^5.0.0"
},
@@ -14,7 +14,7 @@
"dependencies": {
"jsonc-parser": "^3.0.0",
"request-light": "^0.5.4",
"vscode-json-languageservice": "^4.1.6",
"vscode-json-languageservice": "^4.1.9",
"vscode-languageserver": "^7.0.0",
"vscode-uri": "^3.0.2"
},
@@ -23,6 +23,7 @@ export function runSafeAsync<T>(runtime: RuntimeEnvironment, func: () => Thenabl
runtime.timer.setImmediate(() => {
if (token.isCancellationRequested) {
resolve(cancelValue());
return;
}
return func().then(result => {
if (token.isCancellationRequested) {
@@ -22,10 +22,10 @@ request-light@^0.5.4:
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.4.tgz#497a98c6d8ae49536417a5e2d7f383b934f3e38c"
integrity sha512-t3566CMweOFlUk7Y1DJMu5OrtpoZEb6aSTsLQVT3wtrIEJ5NhcY9G/Oqxvjllzl4a15zXfFlcr9q40LbLVQJqw==
vscode-json-languageservice@^4.1.6:
version "4.1.6"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.6.tgz#4275e8daf1cba80273c318f33fbf7a2ede307053"
integrity sha512-DIKb3tcfRtb3tIE6g9SLOl5E9tNSt6kljH08Wa5RwFlVshtXGrDDzttchze4CYy9pJpE9mBtCbRHmLvY1Z1ZXA==
vscode-json-languageservice@^4.1.9:
version "4.1.9"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.9.tgz#fb48edc69e37167c3cafd447c3fa898052d87b61"
integrity sha512-kxNHitUy2fCxmP6vAp0SRLrUSuecUYzzxlC+85cC3jJlFHWmvtCJOzikC+kcUnIdls9fQSB8n0yHs8Sl6taxJw==
dependencies:
jsonc-parser "^3.0.0"
vscode-languageserver-textdocument "^1.0.1"
+4 -4
View File
@@ -51,10 +51,10 @@ semver@^7.3.4:
dependencies:
lru-cache "^6.0.0"
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-jsonrpc@6.0.0:
version "6.0.0"
@@ -12,6 +12,8 @@
["[", "]"],
["(", ")"]
],
"colorizedBracketPairs": [
],
"autoClosingPairs": [
{
"open": "{",
@@ -21,7 +21,7 @@
},
"Insert fenced code block": {
"prefix": "fenced codeblock",
"body": ["```${1:language}", "${TM_SELECTED_TEXT}$0", "```"],
"body": ["```${1|python,c,c++,c#,ruby,go,java,php,htm,css,javascript,json,markdown,console|}", "${TM_SELECTED_TEXT}$0", "```"],
"description": "Insert fenced code block"
},
"Insert heading level 1": {
@@ -64,11 +64,6 @@
"body": ["1. ${1:first}", "2. ${2:second}", "3. ${3:third}", "$0"],
"description": "Insert ordered list"
},
"Insert definition list": {
"prefix": "definition list",
"body": ["${1:term}", ": ${2:definition}", "$0"],
"description": "Insert definition list"
},
"Insert horizontal rule": {
"prefix": "horizontal rule",
"body": "----------\n",
@@ -88,5 +83,5 @@
"prefix": "strikethrough",
"body": "~~${1:${TM_SELECTED_TEXT}}~~",
"description": "Insert strikethrough"
},
},
}
@@ -55,8 +55,8 @@ body.showEditorSelection .code-line {
position: relative;
}
body.showEditorSelection .code-active-line:before,
body.showEditorSelection .code-line:hover:before {
body.showEditorSelection :not(tr).code-active-line:before,
body.showEditorSelection :not(tr).code-line:hover:before {
content: "";
display: block;
position: absolute;
@@ -3,22 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const MarkdownIt = require('markdown-it');
const MarkdownIt: typeof import('markdown-it') = require('markdown-it');
import * as DOMPurify from 'dompurify';
import type * as markdownIt from 'markdown-it';
import type * as MarkdownItToken from 'markdown-it/lib/token';
import type { ActivationFunction } from 'vscode-notebook-renderer';
const sanitizerOptions: DOMPurify.Config = {
ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
};
export function activate(ctx: { workspace: { isTrusted: boolean } }) {
export const activate: ActivationFunction<void> = (ctx) => {
let markdownIt = new MarkdownIt({
html: true
});
addNamedHeaderRendering(markdownIt);
const style = document.createElement('style');
style.classList.add('markdown-style');
style.textContent = `
.emptyMarkdownCell::before {
content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}";
@@ -54,16 +54,19 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
}
h1 {
font-size: 26px;
line-height: 31px;
margin: 0;
margin-bottom: 13px;
font-size: 2.25em;
}
h2 {
font-size: 19px;
margin: 0;
margin-bottom: 10px;
font-size: 1.9em;
}
h3 {
font-size: 1.6em;
}
p {
font-size: 1.1em;
}
h1,
@@ -141,10 +144,13 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
white-space: pre-wrap;
}
`;
document.head.append(style);
const template = document.createElement('template');
template.classList.add('markdown-style');
template.content.appendChild(style);
document.head.appendChild(template);
return {
renderOutputItem: (outputInfo: { text(): string }, element: HTMLElement) => {
renderOutputItem: (outputInfo, element) => {
let previewNode: HTMLElement;
if (!element.shadowRoot) {
const previewRoot = element.attachShadow({ mode: 'open' });
@@ -155,15 +161,19 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
previewRoot.appendChild(defaultStyles.cloneNode(true));
// And then contributed styles
for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) {
previewRoot.appendChild(markdownStyleNode.cloneNode(true));
for (const element of document.getElementsByClassName('markdown-style')) {
if (element instanceof HTMLTemplateElement) {
previewRoot.appendChild(element.content.cloneNode(true));
} else {
previewRoot.appendChild(element.cloneNode(true));
}
}
previewNode = document.createElement('div');
previewNode.id = 'preview';
previewRoot.appendChild(previewNode);
} else {
previewNode = element.shadowRoot.getElementById('preview')! as HTMLElement; // {{SQL CARBON EDIT}} Cast to fix compilation error
previewNode = element.shadowRoot.getElementById('preview')!;
}
const text = outputInfo.text();
@@ -174,24 +184,24 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
previewNode.classList.remove('emptyMarkdownCell');
const unsanitizedRenderedMarkdown = markdownIt.render(text);
previewNode.innerHTML = ctx.workspace.isTrusted
previewNode.innerHTML = <any>(ctx.workspace.isTrusted
? unsanitizedRenderedMarkdown
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions);
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions));
}
},
extendMarkdownIt: (f: (md: typeof markdownIt) => void) => {
f(markdownIt);
}
};
}
};
function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
function addNamedHeaderRendering(md: InstanceType<typeof MarkdownIt>): void {
const slugCounter = new Map<string, number>();
const originalHeaderOpen = md.renderer.rules.heading_open;
md.renderer.rules.heading_open = (tokens: markdownIt.Token[], idx: number, options: any, env: any, self: any) => {
const title = tokens[idx + 1].children.reduce((acc: string, t: any) => acc + t.content, '');
md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
let slug = slugFromHeading(title);
if (slugCounter.has(slug)) {
@@ -202,13 +212,12 @@ function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
slugCounter.set(slug, 0);
}
tokens[idx].attrs = tokens[idx].attrs || [];
tokens[idx].attrs.push(['id', slug]);
tokens[idx].attrSet('id', slug);
if (originalHeaderOpen) {
return originalHeaderOpen(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};
@@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
"moduleResolution": "Node",
"module": "es2020",
"lib": [
"es2018",
@@ -356,14 +356,14 @@
"highlight.js": "^10.4.1",
"markdown-it": "^12.3.2",
"markdown-it-front-matter": "^0.2.1",
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"devDependencies": {
"@types/dompurify": "^2.2.3",
"@types/highlight.js": "10.1.0",
"@types/dompurify": "^2.3.1",
"@types/lodash.throttle": "^4.1.3",
"@types/markdown-it": "0.0.2",
"@types/markdown-it": "12.2.3",
"@types/vscode-notebook-renderer": "^1.60.0",
"@types/vscode-webview": "^1.57.0",
"lodash.throttle": "^4.1.1"
},
@@ -6,10 +6,7 @@
import * as vscode from 'vscode';
import { Command } from '../commandManager';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider } from '../tableOfContentsProvider';
import { isMarkdownFile } from '../util/file';
import { extname } from '../util/path';
import { openDocumentLink } from '../util/openDocumentLink';
type UriComponents = {
readonly scheme?: string;
@@ -25,11 +22,6 @@ export interface OpenDocumentLinkArgs {
readonly fromResource: UriComponents;
}
enum OpenMarkdownLinks {
beside = 'beside',
currentGroup = 'currentGroup',
}
export class OpenDocumentLinkCommand implements Command {
private static readonly id = '_markdown.openDocumentLink';
public readonly id = OpenDocumentLinkCommand.id;
@@ -60,95 +52,9 @@ export class OpenDocumentLinkCommand implements Command {
) { }
public async execute(args: OpenDocumentLinkArgs) {
return OpenDocumentLinkCommand.execute(this.engine, args);
}
public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs): Promise<void> {
const fromResource = vscode.Uri.parse('').with(args.fromResource);
const targetResource = reviveUri(args.parts);
const column = this.getViewColumn(fromResource);
const didOpen = await this.tryOpen(engine, targetResource, args, column);
if (didOpen) {
return;
}
if (extname(targetResource.path) === '') {
await this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column);
return;
}
}
private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn): Promise<boolean> {
const tryUpdateForActiveFile = async (): Promise<boolean> => {
if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) {
await this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment);
return true;
}
}
return false;
};
if (await tryUpdateForActiveFile()) {
return true;
}
let stat: vscode.FileStat;
try {
stat = await vscode.workspace.fs.stat(resource);
if (stat.type === vscode.FileType.Directory) {
await vscode.commands.executeCommand('revealInExplorer', resource);
return true;
}
} catch {
// noop
// If resource doesn't exist, execute `vscode.open` either way so an error
// notification is shown to the user with a create file action #113475
}
try {
await vscode.commands.executeCommand('vscode.open', resource, column);
} catch {
return false;
}
return tryUpdateForActiveFile();
}
private static getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
const config = vscode.workspace.getConfiguration('markdown', resource);
const openLinks = config.get<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
switch (openLinks) {
case OpenMarkdownLinks.beside:
return vscode.ViewColumn.Beside;
case OpenMarkdownLinks.currentGroup:
default:
return vscode.ViewColumn.Active;
}
}
private static async tryRevealLine(engine: MarkdownEngine, editor: vscode.TextEditor, fragment?: string) {
if (fragment) {
const toc = new TableOfContentsProvider(engine, editor.document);
const entry = await toc.lookup(fragment);
if (entry) {
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
}
const lineNumberFragment = fragment.match(/^L(\d+)$/i);
if (lineNumberFragment) {
const line = +lineNumberFragment[1] - 1;
if (!isNaN(line)) {
const lineStart = new vscode.Range(line, 0, line, 0);
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
}
}
}
const targetResource = reviveUri(args.parts).with({ fragment: args.fragment });
return openDocumentLink(this.engine, targetResource, fromResource);
}
}
@@ -158,36 +64,3 @@ function reviveUri(parts: any) {
}
return vscode.Uri.parse('').with(parts);
}
export async function resolveLinkToMarkdownFile(path: string): Promise<vscode.Uri | undefined> {
try {
const standardLink = await tryResolveLinkToMarkdownFile(path);
if (standardLink) {
return standardLink;
}
} catch {
// Noop
}
// If no extension, try with `.md` extension
if (extname(path) === '') {
return tryResolveLinkToMarkdownFile(path + '.md');
}
return undefined;
}
async function tryResolveLinkToMarkdownFile(path: string): Promise<vscode.Uri | undefined> {
const resource = vscode.Uri.file(path);
let document: vscode.TextDocument;
try {
document = await vscode.workspace.openTextDocument(resource);
} catch {
return undefined;
}
if (isMarkdownFile(document)) {
return document.uri;
}
return undefined;
}
@@ -40,7 +40,7 @@ async function showPreview(
const resourceColumn = (vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One;
webviewManager.openDynamicPreview(resource, {
resourceColumn: resourceColumn,
previewColumn: previewSettings.sideBySide ? resourceColumn + 1 : resourceColumn,
previewColumn: previewSettings.sideBySide ? vscode.ViewColumn.Beside : resourceColumn,
locked: !!previewSettings.locked
});
@@ -83,3 +83,4 @@ function registerMarkdownCommands(
commandManager.register(new commands.ReloadPlugins(previewManager, engine));
return commandManager;
}
@@ -15,7 +15,9 @@ function parseLink(
document: vscode.TextDocument,
link: string,
): { uri: vscode.Uri, tooltip?: string } | undefined {
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
const cleanLink = stripAngleBrackets(link);
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(cleanLink);
if (externalSchemeUri) {
// Normalize VS Code links to target currently running version
if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
@@ -89,6 +91,15 @@ function extractDocumentLink(
}
}
/* Used to strip brackets from the markdown link
<http://example.com> will be transformed to
http://example.com
*/
export function stripAngleBrackets(link: string) {
const bracketMatcher = /^<(.*)>$/;
return link.replace(bracketMatcher, '$1');
}
export default class LinkProvider implements vscode.DocumentLinkProvider {
private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g;
private readonly referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g;
@@ -3,13 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Token } from 'markdown-it';
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider } from '../tableOfContentsProvider';
const rangeLimit = 5000;
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvider {
constructor(
@@ -84,10 +88,14 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi
const isStartRegion = (t: string) => /^\s*<!--\s*#?region\b.*-->/.test(t);
const isEndRegion = (t: string) => /^\s*<!--\s*#?endregion\b.*-->/.test(t);
const isRegionMarker = (token: Token) =>
token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap =>
!!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
if (!token.map) {
return false;
}
const isFoldableToken = (token: Token): boolean => {
switch (token.type) {
case 'fence':
case 'list_item_open':
@@ -5,12 +5,12 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { OpenDocumentLinkCommand, resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
import { Logger } from '../logger';
import { MarkdownEngine } from '../markdownEngine';
import { MarkdownContributionProvider } from '../markdownExtensions';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { openDocumentLink, resolveDocumentLink, resolveLinkToMarkdownFile } from '../util/openDocumentLink';
import * as path from '../util/path';
import { WebviewResourceProvider } from '../util/resources';
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
@@ -355,7 +355,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
}
}
vscode.workspace.openTextDocument(this._resource)
await vscode.workspace.openTextDocument(this._resource)
.then(vscode.window.showTextDocument)
.then(undefined, () => {
vscode.window.showErrorMessage(localize('preview.clickOpenFailed', 'Could not open {0}', this._resource.toString()));
@@ -406,6 +406,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
private getWebviewOptions(): vscode.WebviewOptions {
return {
enableScripts: true,
enableForms: false,
localResourceRoots: this.getLocalResourceRoots()
};
}
@@ -428,29 +429,19 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
private async onDidClickPreviewLink(href: string) {
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
if (hrefPath[0] !== '/') {
// We perviously already resolve absolute paths.
// Now make sure we handle relative file paths
const dirnameUri = vscode.Uri.parse(path.dirname(this.resource.path));
hrefPath = vscode.Uri.joinPath(dirnameUri, hrefPath).path;
} else {
// Handle any normalized file paths
hrefPath = vscode.Uri.parse(hrefPath.replace('/file', '')).path;
}
const targetResource = resolveDocumentLink(href, this.resource);
const config = vscode.workspace.getConfiguration('markdown', this.resource);
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
if (openLinks === 'inPreview') {
const markdownLink = await resolveLinkToMarkdownFile(hrefPath);
const markdownLink = await resolveLinkToMarkdownFile(targetResource);
if (markdownLink) {
this.delegate.openPreviewLinkToMarkdownFile(markdownLink, fragment);
this.delegate.openPreviewLinkToMarkdownFile(markdownLink, targetResource.fragment);
return;
}
}
OpenDocumentLinkCommand.execute(this.engine, { parts: { path: hrefPath }, fragment, fromResource: this.resource.toJSON() });
return openDocumentLink(this.engine, targetResource, this.resource);
}
//#region WebviewResourceProvider
@@ -531,7 +522,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
}));
this._register(this.preview.onScroll((scrollInfo) => {
topmostLineMonitor.setPreviousEditorLine(scrollInfo);
topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo);
}));
this._register(topmostLineMonitor.onDidChanged(event => {
@@ -81,7 +81,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
// When at a markdown file, apply existing scroll settings
if (textEditor && textEditor.document && isMarkdownFile(textEditor.document)) {
const line = this._topmostLineMonitor.getPreviousEditorLineByUri(textEditor.document.uri);
const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);
if (line) {
scrollEditorToLine(line, textEditor);
}
@@ -172,7 +172,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
document: vscode.TextDocument,
webview: vscode.WebviewPanel
): Promise<void> {
const lineNumber = this._topmostLineMonitor.getPreviousEditorLineByUri(document.uri);
const lineNumber = this._topmostLineMonitor.getPreviousTextEditorLineByUri(document.uri);
const preview = StaticMarkdownPreview.revive(
document.uri,
webview,
@@ -2,11 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Token } from 'markdown-it';
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider, TocEntry } from '../tableOfContentsProvider';
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export default class MarkdownSmartSelect implements vscode.SelectionRangeProvider {
constructor(
@@ -96,8 +100,8 @@ function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean,
}
}
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): Token[] {
const enclosingTokens = tokens.filter(token => token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] {
const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
if (enclosingTokens.length === 0) {
return [];
}
@@ -105,7 +109,7 @@ function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, p
return sortedTokens;
}
function createBlockRange(block: Token, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
function createBlockRange(block: MarkdownItTokenWithMap, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
if (block.type === 'fence') {
return createFencedRange(block, cursorLine, document, parent);
} else {
@@ -144,7 +148,7 @@ function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode
return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection;
}
function createFencedRange(token: Token, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
const startLine = token.map[0];
const endLine = token.map[1] - 1;
const onFenceLine = cursorLine === startLine || cursorLine === endLine;
@@ -41,7 +41,7 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work
for (let i = 0; i < resources.length; i += maxConcurrent) {
const resourceBatch = resources.slice(i, i + maxConcurrent);
const documentBatch = (await Promise.all(resourceBatch.map(this.getMarkdownDocument))).filter((doc) => !!doc) as SkinnyTextDocument[];
const documentBatch = (await Promise.all(resourceBatch.map(x => this.getMarkdownDocument(x)))).filter((doc) => !!doc) as SkinnyTextDocument[];
docList.push(...documentBatch);
}
return docList;
@@ -3,7 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MarkdownIt, Token } from 'markdown-it';
import MarkdownIt = require('markdown-it');
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
@@ -14,11 +15,34 @@ import { WebviewResourceProvider } from './util/resources';
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
interface MarkdownItConfig {
readonly breaks: boolean;
readonly linkify: boolean;
readonly typographer: boolean;
}
/**
* Adds begin line index to the output via the 'data-line' data attribute.
*/
const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => {
// Set the attribute on every possible token.
md.core.ruler.push('source_map_data_attribute', (state): void => {
for (const token of state.tokens) {
if (token.map && token.type !== 'inline') {
token.attrSet('data-line', String(token.map[0]));
token.attrJoin('class', 'code-line');
}
}
});
// The 'html_block' renderer doesn't respect `attrs`. We need to insert a marker.
const originalHtmlBlockRenderer = md.renderer.rules['html_block'];
if (originalHtmlBlockRenderer) {
md.renderer.rules['html_block'] = (tokens, idx, options, env, self) => (
`<div ${self.renderAttrs(tokens[idx])} ></div>\n` +
originalHtmlBlockRenderer(tokens, idx, options, env, self)
);
}
};
/**
* The markdown-it options that we expose in the settings.
*/
type MarkdownItConfig = Readonly<Required<Pick<MarkdownIt.Options, 'breaks' | 'linkify' | 'typographer'>>>;
class TokenCache {
private cachedDocument?: {
@@ -85,14 +109,15 @@ export class MarkdownEngine {
private async getEngine(config: MarkdownItConfig): Promise<MarkdownIt> {
if (!this.md) {
this.md = import('markdown-it').then(async markdownIt => {
this.md = (async () => {
const markdownIt = await import('markdown-it');
let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md));
for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) {
try {
md = (await plugin)(md);
} catch {
// noop
} catch (e) {
console.error('Could not load markdown it plugin', e);
}
}
@@ -111,18 +136,15 @@ export class MarkdownEngine {
alt: ['paragraph', 'reference', 'blockquote', 'list']
});
for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'fence', 'blockquote_open', 'list_item_open']) {
this.addLineNumberRenderer(md, renderName);
}
this.addImageRenderer(md);
this.addFencedRenderer(md);
this.addLinkNormalizer(md);
this.addLinkValidator(md);
this.addNamedHeaders(md);
this.addLinkRenderer(md);
md.use(pluginSourceMap);
return md;
});
})();
}
const md = await this.md!;
@@ -170,7 +192,7 @@ export class MarkdownEngine {
};
const html = engine.renderer.render(tokens, {
...(engine as any).options,
...engine.options,
...config
}, env);
@@ -199,26 +221,9 @@ export class MarkdownEngine {
};
}
private addLineNumberRenderer(md: MarkdownIt, ruleName: string): void {
const original = md.renderer.rules[ruleName];
md.renderer.rules[ruleName] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
const token = tokens[idx];
if (token.map && token.map.length) {
token.attrSet('data-line', token.map[0] + '');
token.attrJoin('class', 'code-line');
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
}
};
}
private addImageRenderer(md: MarkdownIt): void {
const original = md.renderer.rules.image;
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: RenderEnv, self: any) => {
md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => {
const token = tokens[idx];
token.attrJoin('class', 'loading');
@@ -237,20 +242,24 @@ export class MarkdownEngine {
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};
}
private addFencedRenderer(md: MarkdownIt): void {
const original = md.renderer.rules['fenced'];
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options, env, self) => {
const token = tokens[idx];
if (token.map && token.map.length) {
token.attrJoin('class', 'hljs');
}
return original(tokens, idx, options, env, self);
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options);
}
};
}
@@ -282,8 +291,8 @@ export class MarkdownEngine {
private addNamedHeaders(md: MarkdownIt): void {
const original = md.renderer.rules.heading_open;
md.renderer.rules.heading_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
const title = tokens[idx + 1].children.reduce((acc: string, t: any) => acc + t.content, '');
md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => {
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
let slug = this.slugifier.fromHeading(title);
if (this._slugCount.has(slug.value)) {
@@ -294,30 +303,31 @@ export class MarkdownEngine {
this._slugCount.set(slug.value, 0);
}
tokens[idx].attrs = tokens[idx].attrs || [];
tokens[idx].attrs.push(['id', slug.value]);
tokens[idx].attrSet('id', slug.value);
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};
}
private addLinkRenderer(md: MarkdownIt): void {
const old_render = md.renderer.rules.link_open || ((tokens: Token[], idx: number, options: any, _env: any, self: any) => {
return self.renderToken(tokens, idx, options);
});
const original = md.renderer.rules.link_open;
md.renderer.rules.link_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
md.renderer.rules.link_open = (tokens: Token[], idx: number, options, env, self) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const href = token.attrs[hrefIndex][1];
token.attrPush(['data-href', href]);
const href = token.attrGet('href');
// A string, including empty string, may be `href`.
if (typeof href === 'string') {
token.attrSet('data-href', href);
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options);
}
return old_render(tokens, idx, options, env, self);
};
}
@@ -366,7 +376,7 @@ export class MarkdownEngine {
}
}
async function getMarkdownOptions(md: () => MarkdownIt) {
async function getMarkdownOptions(md: () => MarkdownIt): Promise<MarkdownIt.Options> {
const hljs = await import('highlight.js');
return {
html: true,
@@ -60,6 +60,10 @@ export class TableOfContentsProvider {
const existingSlugEntries = new Map<string, { count: number }>();
for (const heading of tokens.filter(token => token.type === 'heading_open')) {
if (!heading.map) {
continue;
}
const lineNumber = heading.map[0];
const line = document.lineAt(lineNumber);
@@ -94,6 +94,17 @@ suite('Markdown Document links', () => {
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1);
});
test('Should navigate to line number within non-md file', async () => {
await withFileContents(testFileA, '[b](sub/foo.txt#L3)');
const [link] = await getLinksForFile(testFileA);
await executeLink(link);
assertActiveDocumentUri(workspaceFile('sub', 'foo.txt'));
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 2);
});
test('Should navigate to fragment within current file', async () => {
await withFileContents(testFileA, joinLines(
'[](a#header)',
@@ -15,7 +15,7 @@ const testFileName = vscode.Uri.file('test.md');
suite('markdown.engine', () => {
suite('rendering', () => {
const input = '# hello\n\nworld!';
const output = '<h1 id="hello" data-line="0" class="code-line">hello</h1>\n'
const output = '<h1 data-line="0" class="code-line" id="hello">hello</h1>\n'
+ '<p data-line="2" class="code-line">world!</p>\n';
test('Renders a document', async () => {
@@ -0,0 +1,161 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider } from '../tableOfContentsProvider';
import { isMarkdownFile } from './file';
import { extname } from './path';
export interface OpenDocumentLinkArgs {
readonly parts: vscode.Uri;
readonly fragment: string;
readonly fromResource: vscode.Uri;
}
enum OpenMarkdownLinks {
beside = 'beside',
currentGroup = 'currentGroup',
}
export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vscode.Uri {
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
if (hrefPath[0] === '/') {
// Absolute path. Try to resolve relative to the workspace
const workspace = vscode.workspace.getWorkspaceFolder(markdownFile);
if (workspace) {
return vscode.Uri.joinPath(workspace.uri, hrefPath.slice(1)).with({ fragment });
}
}
// Relative path. Resolve relative to the md file
const dirnameUri = markdownFile.with({ path: path.dirname(markdownFile.path) });
return vscode.Uri.joinPath(dirnameUri, hrefPath).with({ fragment });
}
export async function openDocumentLink(engine: MarkdownEngine, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise<void> {
const column = getViewColumn(fromResource);
if (await tryNavigateToFragmentInActiveEditor(engine, targetResource)) {
return;
}
let targetResourceStat: vscode.FileStat | undefined;
try {
targetResourceStat = await vscode.workspace.fs.stat(targetResource);
} catch {
// noop
}
if (typeof targetResourceStat === 'undefined') {
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
if (extname(targetResource.path) === '') {
const dotMdResource = targetResource.with({ path: targetResource.path + '.md' });
try {
const stat = await vscode.workspace.fs.stat(dotMdResource);
if (stat.type === vscode.FileType.File) {
await tryOpenMdFile(engine, dotMdResource, column);
return;
}
} catch {
// noop
}
}
} else if (targetResourceStat.type === vscode.FileType.Directory) {
return vscode.commands.executeCommand('revealInExplorer', targetResource);
}
await tryOpenMdFile(engine, targetResource, column);
}
async function tryOpenMdFile(engine: MarkdownEngine, resource: vscode.Uri, column: vscode.ViewColumn): Promise<boolean> {
await vscode.commands.executeCommand('vscode.open', resource.with({ fragment: '' }), column);
return tryNavigateToFragmentInActiveEditor(engine, resource);
}
async function tryNavigateToFragmentInActiveEditor(engine: MarkdownEngine, resource: vscode.Uri): Promise<boolean> {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor?.document.uri.fsPath === resource.fsPath) {
if (isMarkdownFile(activeEditor.document)) {
if (await tryRevealLineUsingTocFragment(engine, activeEditor, resource.fragment)) {
return true;
}
}
tryRevealLineUsingLineFragment(activeEditor, resource.fragment);
return true;
}
return false;
}
function getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
const config = vscode.workspace.getConfiguration('markdown', resource);
const openLinks = config.get<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
switch (openLinks) {
case OpenMarkdownLinks.beside:
return vscode.ViewColumn.Beside;
case OpenMarkdownLinks.currentGroup:
default:
return vscode.ViewColumn.Active;
}
}
async function tryRevealLineUsingTocFragment(engine: MarkdownEngine, editor: vscode.TextEditor, fragment: string): Promise<boolean> {
const toc = new TableOfContentsProvider(engine, editor.document);
const entry = await toc.lookup(fragment);
if (entry) {
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
return true;
}
return false;
}
function tryRevealLineUsingLineFragment(editor: vscode.TextEditor, fragment: string): boolean {
const lineNumberFragment = fragment.match(/^L(\d+)$/i);
if (lineNumberFragment) {
const line = +lineNumberFragment[1] - 1;
if (!isNaN(line)) {
const lineStart = new vscode.Range(line, 0, line, 0);
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
return true;
}
}
return false;
}
export async function resolveLinkToMarkdownFile(resource: vscode.Uri): Promise<vscode.Uri | undefined> {
try {
const standardLink = await tryResolveLinkToMarkdownFile(resource);
if (standardLink) {
return standardLink;
}
} catch {
// Noop
}
// If no extension, try with `.md` extension
if (extname(resource.path) === '') {
return tryResolveLinkToMarkdownFile(resource.with({ path: resource.path + '.md' }));
}
return undefined;
}
async function tryResolveLinkToMarkdownFile(resource: vscode.Uri): Promise<vscode.Uri | undefined> {
let document: vscode.TextDocument;
try {
document = await vscode.workspace.openTextDocument(resource);
} catch {
return undefined;
}
if (isMarkdownFile(document)) {
return document.uri;
}
return undefined;
}
@@ -16,15 +16,15 @@ export class TopmostLineMonitor extends Disposable {
private readonly pendingUpdates = new Map<string, number>();
private readonly throttle = 50;
private previousEditorInfo = new Map<string, LastScrollLocation>();
public isPrevEditorCustom = false;
private previousTextEditorInfo = new Map<string, LastScrollLocation>();
private previousStaticEditorInfo = new Map<string, LastScrollLocation>();
constructor() {
super();
if (vscode.window.activeTextEditor) {
const line = getVisibleLine(vscode.window.activeTextEditor);
this.setPreviousEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 });
this.setPreviousTextEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 });
}
this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
@@ -32,7 +32,7 @@ export class TopmostLineMonitor extends Disposable {
const line = getVisibleLine(event.textEditor);
if (typeof line === 'number') {
this.updateLine(event.textEditor.document.uri, line);
this.setPreviousEditorLine({ uri: event.textEditor.document.uri, line: line });
this.setPreviousTextEditorLine({ uri: event.textEditor.document.uri, line: line });
}
}
}));
@@ -41,12 +41,24 @@ export class TopmostLineMonitor extends Disposable {
private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>());
public readonly onDidChanged = this._onChanged.event;
public setPreviousEditorLine(scrollLocation: LastScrollLocation): void {
this.previousEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void {
this.previousStaticEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
}
public getPreviousEditorLineByUri(resource: vscode.Uri): number | undefined {
const scrollLoc = this.previousEditorInfo.get(resource.toString());
public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined {
const scrollLoc = this.previousStaticEditorInfo.get(resource.toString());
this.previousStaticEditorInfo.delete(resource.toString());
return scrollLoc?.line;
}
public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void {
this.previousTextEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
}
public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined {
const scrollLoc = this.previousTextEditorInfo.get(resource.toString());
this.previousTextEditorInfo.delete(resource.toString());
return scrollLoc?.line;
}
@@ -0,0 +1,5 @@
1
2
3
4
5
+30 -19
View File
@@ -2,19 +2,17 @@
# yarn lockfile v1
"@types/dompurify@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.2.3.tgz#6e89677a07902ac1b6821c345f34bd85da239b08"
integrity sha512-CLtc2mZK8+axmrz1JqtpklO/Kvn38arGc8o1l3UVopZaXXuer9ONdZwJ/9f226GrhRLtUmLr9WrvZsRSNpS8og==
"@types/dompurify@^2.3.1":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f"
integrity sha512-nnVQSgRVuZ/843oAfhA25eRSNzUFcBPk/LOiw5gm8mD9/X7CNcbRkQu/OsjCewO8+VIYfPxUnXvPEVGenw14+w==
dependencies:
"@types/trusted-types" "*"
"@types/highlight.js@10.1.0":
version "10.1.0"
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-10.1.0.tgz#89bb0c202997d7a90a07bd2ec1f7d00c56bb90b4"
integrity sha512-77hF2dGBsOgnvZll1vymYiNUtqJ8cJfXPD6GG/2M0aLRc29PkvB7Au6sIDjIEFcSICBhCh2+Pyq6WSRS7LUm6A==
dependencies:
highlight.js "*"
"@types/linkify-it@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
"@types/lodash.throttle@^4.1.3":
version "4.1.3"
@@ -28,16 +26,29 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80"
integrity sha512-ufQcVg4daO8xQ5kopxRHanqFdL4AI7ondQkV+2f+7mz3gvp0LkBx2zBRC6hfs3T87mzQFmf5Fck7Fi145Ul6NQ==
"@types/markdown-it@0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
"@types/markdown-it@12.2.3":
version "12.2.3"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
dependencies:
"@types/linkify-it" "*"
"@types/mdurl" "*"
"@types/mdurl@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
"@types/trusted-types@*":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/vscode-notebook-renderer@^1.60.0":
version "1.60.0"
resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.60.0.tgz#8a67d561f48ddf46a95dfa9f712a79c72c7b8f7a"
integrity sha512-u7TD2uuEZTVuitx0iijOJdKI0JLiQP6PsSBSRy2XmHXUOXcp5p1S56NrjOEDoF+PIHd3NL3eO6KTRSf5nukDqQ==
"@types/vscode-webview@^1.57.0":
version "1.57.0"
resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf"
@@ -58,7 +69,7 @@ entities@~2.1.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
highlight.js@*, highlight.js@^10.4.1:
highlight.js@^10.4.1:
version "10.7.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955"
integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA==
@@ -101,10 +112,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"
+7 -5
View File
@@ -3,13 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as markdownIt from 'markdown-it';
import type { RendererContext } from 'vscode-notebook-renderer';
const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css');
export async function activate(ctx: {
getRenderer: (id: string) => Promise<any | undefined>
}) {
const markdownItRenderer = await ctx.getRenderer('markdownItRenderer');
export async function activate(ctx: RendererContext<void>) {
const markdownItRenderer = (await ctx.getRenderer('markdownItRenderer')) as undefined | any;
if (!markdownItRenderer) {
throw new Error('Could not load markdownItRenderer');
}
@@ -41,6 +40,9 @@ export async function activate(ctx: {
const katex = require('@iktakahiro/markdown-it-katex');
markdownItRenderer.extendMarkdownIt((md: markdownIt.MarkdownIt) => {
return md.use(katex, { globalGroup: true });
return md.use(katex, {
globalGroup: true,
enableBareBlocks: true,
});
});
}
+2 -1
View File
@@ -60,7 +60,8 @@
"@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git"
},
"devDependencies": {
"@types/markdown-it": "^0.0.0"
"@types/markdown-it": "^0.0.0",
"@types/vscode-notebook-renderer": "^1.60.0"
},
"repository": {
"type": "git",
+6 -1
View File
@@ -4,7 +4,7 @@
"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git":
version "4.0.1"
resolved "https://github.com/mjbvz/markdown-it-katex.git#2bf0b89c6c22ef0b585f55ccab66d1f7c5356bea"
resolved "https://github.com/mjbvz/markdown-it-katex.git#820d9025ad84937eb3f9f7efbc1be7595e20b19f"
dependencies:
katex "^0.13.0"
@@ -13,6 +13,11 @@
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.0.tgz#8f6acaa5e3245e275f684e95deb3e518d1c6ab16"
integrity sha1-j2rKpeMkXidfaE6V3rPlGNHGqxY=
"@types/vscode-notebook-renderer@^1.60.0":
version "1.60.0"
resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.60.0.tgz#8a67d561f48ddf46a95dfa9f712a79c72c7b8f7a"
integrity sha512-u7TD2uuEZTVuitx0iijOJdKI0JLiQP6PsSBSRy2XmHXUOXcp5p1S56NrjOEDoF+PIHd3NL3eO6KTRSf5nukDqQ==
commander@^6.0.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
@@ -12,7 +12,11 @@ const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
node: false,
node: {
global: true,
__filename: false,
__dirname: false,
},
entry: {
extension: './src/extension.ts',
},
@@ -23,11 +27,6 @@ module.exports = withBrowserDefaults({
alias: {
'./env/node': path.resolve(__dirname, 'src/env/browser'),
'./authServer': path.resolve(__dirname, 'src/env/browser/authServer'),
'buffer': path.resolve(__dirname, 'node_modules/buffer/index.js'),
'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'),
'randombytes': path.resolve(__dirname, 'node_modules/randombytes/browser.js'),
'stream': path.resolve(__dirname, 'node_modules/stream/index.js'),
'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js')
}
}
});
@@ -54,11 +54,11 @@
"dependencies": {
"buffer": "^5.6.0",
"node-fetch": "^2.6.7",
"randombytes": "github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971",
"randombytes": "~2.1.0",
"sha.js": "2.4.11",
"stream": "0.0.2",
"uuid": "^8.2.0",
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"repository": {
@@ -100,7 +100,7 @@ export class AzureActiveDirectoryService {
private _tokens: IToken[] = [];
private _refreshTimeouts: Map<string, NodeJS.Timeout> = new Map<string, NodeJS.Timeout>();
private _uriHandler: UriEventHandler;
private _disposables: vscode.Disposable[] = [];
private _disposable: vscode.Disposable;
// Used to keep track of current requests when not using the local server approach.
private _pendingStates = new Map<string, string[]>();
@@ -112,51 +112,59 @@ export class AzureActiveDirectoryService {
constructor(private _context: vscode.ExtensionContext) {
this._keychain = new Keychain(_context);
this._uriHandler = new UriEventHandler();
this._disposables.push(vscode.window.registerUriHandler(this._uriHandler));
this._disposable = vscode.Disposable.from(
vscode.window.registerUriHandler(this._uriHandler),
this._context.secrets.onDidChange(() => this.checkForUpdates()));
}
public async initialize(): Promise<void> {
const storedData = await this._keychain.getToken() || await this._keychain.tryMigrate();
if (storedData) {
try {
const sessions = this.parseStoredData(storedData);
const refreshes = sessions.map(async session => {
if (!session.refreshToken) {
return Promise.resolve();
}
try {
await this.refreshToken(session.refreshToken, session.scope, session.id);
} catch (e) {
if (e.message === REFRESH_NETWORK_FAILURE) {
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
if (!didSucceedOnRetry) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
this.pollForReconnect(session.id, session.refreshToken, session.scope);
}
} else {
await this.removeSession(session.id);
}
}
});
await Promise.all(refreshes);
} catch (e) {
Logger.info('Failed to initialize stored data');
await this.clearSessions();
}
Logger.info('Reading sessions from keychain...');
const storedData = await this._keychain.getToken();
if (!storedData) {
Logger.info('No stored sessions found.');
return;
}
Logger.info('Got stored sessions!');
this._disposables.push(this._context.secrets.onDidChange(() => this.checkForUpdates));
try {
const sessions = this.parseStoredData(storedData);
const refreshes = sessions.map(async session => {
Logger.trace(`Read the following session from the keychain with the following scopes: ${session.scope}`);
if (!session.refreshToken) {
Logger.trace(`Session with the following scopes does not have a refresh token so we will not try to refresh it: ${session.scope}`);
return Promise.resolve();
}
try {
await this.refreshToken(session.refreshToken, session.scope, session.id);
} catch (e) {
// If we aren't connected to the internet, then wait and try to refresh again later.
if (e.message === REFRESH_NETWORK_FAILURE) {
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
if (!didSucceedOnRetry) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
this.pollForReconnect(session.id, session.refreshToken, session.scope);
}
} else {
await this.removeSession(session.id);
}
}
});
await Promise.all(refreshes);
} catch (e) {
Logger.error(`Failed to initialize stored data: ${e}`);
await this.clearSessions();
}
}
private parseStoredData(data: string): IStoredSession[] {
@@ -263,8 +271,8 @@ export class AzureActiveDirectoryService {
private async resolveAccessAndIdTokens(token: IToken): Promise<IMicrosoftTokens> {
if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) {
token.expiresAt
? Logger.info(`Token available from cache, expires in ${token.expiresAt - Date.now()} milliseconds`)
: Logger.info('Token available from cache');
? Logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`)
: Logger.info('Token available from cache (for scopes ${token.scope})');
return Promise.resolve({
accessToken: token.accessToken,
idToken: token.idToken
@@ -272,7 +280,7 @@ export class AzureActiveDirectoryService {
}
try {
Logger.info('Token expired or unavailable, trying refresh');
Logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`);
const refreshedToken = await this.refreshToken(token.refreshToken, token.scope, token.sessionId);
if (refreshedToken.accessToken) {
return {
@@ -301,98 +309,97 @@ export class AzureActiveDirectoryService {
}
async getSessions(scopes?: string[]): Promise<vscode.AuthenticationSession[]> {
Logger.info(`Getting sessions for ${scopes?.join(',') ?? 'all scopes'}...`);
if (!scopes) {
return this.sessions;
const sessions = await this.sessions;
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
return sessions;
}
const orderedScopes = scopes.sort().join(' ');
const matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
Logger.info(`Got ${matchingTokens.length} sessions for ${scopes?.join(',')}...`);
return Promise.all(matchingTokens.map(token => this.convertToSession(token)));
}
public async createSession(scope: string): Promise<vscode.AuthenticationSession> {
Logger.info('Logging in...');
Logger.info(`Logging in for the following scopes: ${scope}`);
if (!scope.includes('offline_access')) {
Logger.info('Warning: The \'offline_access\' scope was not included, so the generated token will not be able to be refreshed.');
}
return new Promise(async (resolve, reject) => {
const runsRemote = vscode.env.remoteName !== undefined;
const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
const runsRemote = vscode.env.remoteName !== undefined;
const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web;
if (runsRemote || runsServerless) {
return this.loginWithoutLocalServer(scope);
}
if (runsRemote || runsServerless) {
resolve(this.loginWithoutLocalServer(scope));
return;
const nonce = randomBytes(16).toString('base64');
const { server, redirectPromise, codePromise } = createServer(nonce);
let token: IToken | undefined;
try {
const port = await startServer(server);
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
const redirectReq = await redirectPromise;
if ('err' in redirectReq) {
const { err, res } = redirectReq;
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
throw err;
}
const nonce = randomBytes(16).toString('base64');
const { server, redirectPromise, codePromise } = createServer(nonce);
const host = redirectReq.req.headers.host || '';
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
redirectReq.res.writeHead(302, { Location: loginUrl });
redirectReq.res.end();
const codeRes = await codePromise;
const res = codeRes.res;
let token: IToken | undefined;
try {
const port = await startServer(server);
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
const redirectReq = await redirectPromise;
if ('err' in redirectReq) {
const { err, res } = redirectReq;
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
throw err;
if ('err' in codeRes) {
throw codeRes.err;
}
const host = redirectReq.req.headers.host || '';
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
await redirectReq.res.writeHead(302, { Location: loginUrl });
redirectReq.res.end();
const codeRes = await codePromise;
const res = codeRes.res;
try {
if ('err' in codeRes) {
throw codeRes.err;
}
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope);
this.setToken(token, scope);
Logger.info('Login successful');
res.writeHead(302, { Location: '/' });
const session = await this.convertToSession(token);
resolve(session);
res.end();
} catch (err) {
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
res.end();
reject(err.message);
}
} catch (e) {
Logger.error(e.message);
// If the error was about starting the server, try directly hitting the login endpoint instead
if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
await this.loginWithoutLocalServer(scope);
}
reject(e.message);
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope);
this.setToken(token, scope);
Logger.info(`Login successful for scopes: ${scope}`);
res.writeHead(302, { Location: '/' });
const session = await this.convertToSession(token);
return session;
} catch (err) {
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
throw err;
} finally {
setTimeout(() => {
server.close();
}, 5000);
res.end();
}
});
} catch (e) {
Logger.error(`Error creating session for scopes: ${scope} Error: ${e}`);
// If the error was about starting the server, try directly hitting the login endpoint instead
if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') {
return this.loginWithoutLocalServer(scope);
}
throw e;
} finally {
setTimeout(() => {
server.close();
}, 5000);
}
}
public dispose(): void {
this._disposables.forEach(disposable => disposable.dispose());
this._disposables = [];
this._disposable.dispose();
}
private getCallbackEnvironment(callbackUri: vscode.Uri): string {
@@ -554,7 +561,7 @@ export class AzureActiveDirectoryService {
}
private async exchangeCodeForToken(code: string, codeVerifier: string, scope: string): Promise<IToken> {
Logger.info('Exchanging login code for token');
Logger.info(`Exchanging login code for token for scopes: ${scope}`);
try {
const postData = querystring.stringify({
grant_type: 'authorization_code',
@@ -579,21 +586,21 @@ export class AzureActiveDirectoryService {
});
if (result.ok) {
Logger.info('Exchanging login code for token success');
const json = await result.json();
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
return this.getTokenFromResponse(json, scope);
} else {
Logger.error('Exchanging login code for token failed');
Logger.error(`Exchanging login code for token (for scopes: ${scope}) failed: ${await result.text()}`);
throw new Error('Unable to login.');
}
} catch (e) {
Logger.error(e.message);
Logger.error(`Error exchanging code for token (for scopes ${scope}): ${e}`);
throw e;
}
}
private async refreshToken(refreshToken: string, scope: string, sessionId: string): Promise<IToken> {
Logger.info('Refreshing token...');
Logger.info(`Refreshing token for scopes: ${scope}`);
const postData = querystring.stringify({
refresh_token: refreshToken,
client_id: clientId,
@@ -615,7 +622,7 @@ export class AzureActiveDirectoryService {
body: postData
});
} catch (e) {
Logger.error('Refreshing token failed');
Logger.error(`Refreshing token failed (for scopes: ${scope}) Error: ${e}`);
throw new Error(REFRESH_NETWORK_FAILURE);
}
@@ -624,14 +631,14 @@ export class AzureActiveDirectoryService {
const json = await result.json();
const token = this.getTokenFromResponse(json, scope, sessionId);
this.setToken(token, scope);
Logger.info('Token refresh success');
Logger.info(`Token refresh success for scopes: ${token.scope}`);
return token;
} else {
throw new Error('Bad request.');
}
} catch (e) {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
Logger.error(`Refreshing token failed: ${result.statusText}`);
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${result.statusText}`);
throw new Error('Refreshing token failed');
}
}
@@ -672,7 +679,7 @@ export class AzureActiveDirectoryService {
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise<boolean> {
return new Promise((resolve, _) => {
if (attempts === 3) {
Logger.error('Token refresh failed after 3 attempts');
Logger.error(`Token refresh (for scopes: ${scope}) failed after 3 attempts`);
return resolve(false);
}
@@ -7,8 +7,6 @@ import * as vscode from 'vscode';
import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper';
import TelemetryReporter from 'vscode-extension-telemetry';
export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access';
export async function activate(context: vscode.ExtensionContext) {
const { name, version, aiKey } = context.extension.packageJSON as { name: string, version: string, aiKey: string };
const telemetryReporter = new TelemetryReporter(name, version, aiKey);
@@ -3,47 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// keytar depends on a native module shipped in vscode, so this is
// how we load it
import * as keytarType from 'keytar';
import * as vscode from 'vscode';
import Logger from './logger';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
function getKeytar(): Keytar | undefined {
try {
return require('keytar');
} catch (err) {
console.log(err);
}
return undefined;
}
export type Keytar = {
getPassword: typeof keytarType['getPassword'];
setPassword: typeof keytarType['setPassword'];
deletePassword: typeof keytarType['deletePassword'];
};
const OLD_SERVICE_ID = `${vscode.env.uriScheme}-microsoft.login`;
const SERVICE_ID = `microsoft.login`;
const ACCOUNT_ID = 'account';
export class Keychain {
private keytar: Keytar;
constructor(private context: vscode.ExtensionContext) {
const keytar = getKeytar();
if (!keytar) {
throw new Error('System keychain unavailable');
}
this.keytar = keytar;
}
constructor(private context: vscode.ExtensionContext) { }
async setToken(token: string): Promise<void> {
@@ -87,19 +57,4 @@ export class Keychain {
return Promise.resolve(undefined);
}
}
async tryMigrate(): Promise<string | null> {
try {
const oldValue = await this.keytar.getPassword(OLD_SERVICE_ID, ACCOUNT_ID);
if (oldValue) {
await this.setToken(oldValue);
await this.keytar.deletePassword(OLD_SERVICE_ID, ACCOUNT_ID);
}
return oldValue;
} catch (_) {
// Ignore
return Promise.resolve(null);
}
}
}
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
type LogLevel = 'Info' | 'Error';
type LogLevel = 'Trace' | 'Info' | 'Error';
class Log {
private output: vscode.OutputChannel;
@@ -24,6 +24,10 @@ class Log {
return data.toString();
}
public trace(message: string, data?: any): void {
this.logLevel('Trace', message, data);
}
public info(message: string, data?: any): void {
this.logLevel('Info', message, data);
}
@@ -112,9 +112,10 @@ node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
"randombytes@github:rmacfarlane/randombytes#b28d4ecee46262801ea09f15fa1f1513a05c5971":
randombytes@~2.1.0:
version "2.1.0"
resolved "https://codeload.github.com/rmacfarlane/randombytes/tar.gz/b28d4ecee46262801ea09f15fa1f1513a05c5971"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
@@ -153,10 +154,10 @@ uuid@^8.2.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"
+1 -1
View File
@@ -114,7 +114,7 @@ export class AccountFeature implements StaticFeature {
}
// find tenant
const tenant = account.properties.tenants.find(tenant => tenant.id === request.tenantId);
const tenant = account.properties.tenants.find((tenant: any) => tenant.id === request.tenantId);
if (!tenant) {
console.log(`Failed to find tenant ${request.tenantId} in account ${account.displayInfo.displayName} when refreshing security token`);
throw Error(localizedConstants.failedToFindTenants(request.tenantId, account.displayInfo.displayName));
+1 -1
View File
@@ -4,7 +4,7 @@
"license": "MIT",
"description": "Dependencies shared by all extensions",
"dependencies": {
"typescript": "^4.4.1-rc"
"typescript": "^4.8.0-dev.20220614"
},
"scripts": {
"postinstall": "node ./postinstall"
+2 -1
View File
@@ -24,7 +24,8 @@
".gyp",
".gypi",
".pyi",
".ipy"
".ipy",
".pyt"
],
"aliases": [
"Python",
+2 -2
View File
@@ -6,11 +6,11 @@
"git": {
"name": "Ikuyadeu/vscode-R",
"repositoryUrl": "https://github.com/Ikuyadeu/vscode-R",
"commitHash": "f98bd30417c203876969408440f656f56eba80d8"
"commitHash": "c6a9803fbda262ea68c427a2339bddafed41a9d5"
}
},
"license": "MIT",
"version": "2.0.0"
"version": "2.1.0"
}
],
"version": 1
+3 -4
View File
@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/Ikuyadeu/vscode-R/commit/f98bd30417c203876969408440f656f56eba80d8",
"version": "https://github.com/Ikuyadeu/vscode-R/commit/c6a9803fbda262ea68c427a2339bddafed41a9d5",
"name": "R",
"scopeName": "source.r",
"patterns": [
@@ -332,8 +332,8 @@
"function-declarations": {
"patterns": [
{
"begin": "^\\s*([a-zA-Z._][\\w.:]*)\\s*(<<?-|=)\\s*(?=function\\s*\\()",
"beginCaptures": {
"match": "((?:`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`)|(?:[[:alpha:].][[:alnum:]._]*))\\s*(<?<-|=(?!=))\\s*(function)",
"captures": {
"1": {
"name": "entity.name.function.r"
},
@@ -344,7 +344,6 @@
"name": "keyword.control.r"
}
},
"end": "(?<=\\))",
"name": "meta.function.r",
"patterns": [
{
@@ -23,14 +23,17 @@ export class TestChildProcessPromise<T> implements cp.ChildProcessPromise {
}
resolve!: (value: T | PromiseLike<T>) => void;
reject!: (reason?: any) => void;
then<TResult1 = T, TResult2 = never>(onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2> {
then<TResult1 = T, TResult2 = never>(onFulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2> {
return this._promise.then(onFulfilled, onRejected);
}
catch<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult> {
catch<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult | any> {
return this._promise.catch(onRejected);
}
[Symbol.toStringTag]: string;
finally(onFinally?: (() => void) | null): Promise<T> {
[Symbol.toStringTag]: string = '';
finally(onFinally?: (() => void) | null): Promise<T | any> {
return this._promise.finally(onFinally);
}
stdin: any = this._event;
@@ -25,7 +25,7 @@ export class RadioGroupLoadingComponentBuilder implements azdata.ComponentBuilde
}
withProperties<U>(properties: U): azdata.ComponentBuilder<azdata.LoadingComponent, azdata.LoadingComponentProperties> {
return this._optionsLoadingBuilder.withProps(properties);
return this._optionsLoadingBuilder.withProps(<any>properties);
}
withProps(properties: azdata.LoadingComponentProperties): azdata.ComponentBuilder<azdata.LoadingComponent, azdata.LoadingComponentProperties> {
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+1
View File
@@ -6,6 +6,7 @@
"enableProposedApi": true,
"publisher": "vscode",
"license": "MIT",
"icon": "images/icon.png",
"engines": {
"vscode": "^1.39.0"
},
+7 -8
View File
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import * as pathUtils from 'path';
const FILE_LINE_REGEX = /^(\S.*):$/;
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/;
const ELISION_REGEX = /⟪ ([0-9]+) characters skipped ⟫/g;
const SEARCH_RESULT_SELECTOR = { language: 'search-result', exclusive: true };
const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:'];
@@ -220,10 +220,9 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel
const resultLine = RESULT_LINE_REGEX.exec(line);
if (resultLine) {
const [, indentation, _lineNumber, seperator, resultIndentation] = resultLine;
const [, indentation, _lineNumber, separator] = resultLine;
const lineNumber = +_lineNumber - 1;
const resultStart = (indentation + _lineNumber + seperator + resultIndentation).length;
const metadataOffset = (indentation + _lineNumber + seperator).length;
const metadataOffset = (indentation + _lineNumber + separator).length;
const targetRange = new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length);
let locations: Required<vscode.LocationLink>[] = [];
@@ -233,12 +232,12 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel
targetRange,
targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, 1),
targetUri: currentTarget,
originSelectionRange: new vscode.Range(i, 0, i, resultStart),
originSelectionRange: new vscode.Range(i, 0, i, metadataOffset - 1),
});
let lastEnd = resultStart;
let lastEnd = metadataOffset;
let offset = 0;
ELISION_REGEX.lastIndex = resultStart;
ELISION_REGEX.lastIndex = metadataOffset;
for (let match: RegExpExecArray | null; (match = ELISION_REGEX.exec(line));) {
locations.push({
targetRange,
@@ -261,7 +260,7 @@ function parseSearchResults(document: vscode.TextDocument, token?: vscode.Cancel
}
currentTargetLocations?.push(...locations);
links[i] = { type: 'result', locations, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
links[i] = { type: 'result', locations, isContext: separator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
}
}
+3 -2
View File
@@ -24,7 +24,8 @@
"onCommand:simpleBrowser.show",
"onCommand:simpleBrowser.api.open",
"onOpenExternalUri:http",
"onOpenExternalUri:https"
"onOpenExternalUri:https",
"onWebviewPanel:simpleBrowser.view"
],
"capabilities": {
"virtualWorkspaces": true,
@@ -65,7 +66,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"vscode-extension-telemetry": "0.2.8",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"devDependencies": {
@@ -101,6 +101,8 @@ onceDocumentLoaded(() => {
} catch {
iframe.src = rawUrl;
}
vscode.setState({ url: rawUrl });
}
});
@@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { SimpleBrowserManager } from './simpleBrowserManager';
import { SimpleBrowserView } from './simpleBrowserView';
declare class URL {
constructor(input: string, base?: string | URL);
@@ -38,6 +39,12 @@ export function activate(context: vscode.ExtensionContext) {
const manager = new SimpleBrowserManager(context.extensionUri);
context.subscriptions.push(manager);
context.subscriptions.push(vscode.window.registerWebviewPanelSerializer(SimpleBrowserView.viewType, {
deserializeWebviewPanel: async (panel, state) => {
manager.restore(panel, state);
}
}));
context.subscriptions.push(vscode.commands.registerCommand(showCommand, async (url?: string) => {
if (!url) {
url = await vscode.window.showInputBox({
@@ -23,16 +23,27 @@ export class SimpleBrowserManager {
if (this._activeView) {
this._activeView.show(url, options);
} else {
const view = new SimpleBrowserView(this.extensionUri, url, options);
view.onDispose(() => {
if (this._activeView === view) {
this._activeView = undefined;
}
});
const view = SimpleBrowserView.create(this.extensionUri, url, options);
this.registerWebviewListeners(view);
this._activeView = view;
}
}
public restore(panel: vscode.WebviewPanel, state: any): void {
const url = state?.url ?? '';
const view = SimpleBrowserView.restore(this.extensionUri, url, panel);
this.registerWebviewListeners(view);
return;
}
private registerWebviewListeners(view: SimpleBrowserView) {
view.onDispose(() => {
if (this._activeView === view) {
this._activeView = undefined;
}
});
}
}
@@ -24,23 +24,41 @@ export class SimpleBrowserView extends Disposable {
private readonly _onDidDispose = this._register(new vscode.EventEmitter<void>());
public readonly onDispose = this._onDidDispose.event;
constructor(
private readonly extensionUri: vscode.Uri,
public static create(
extensionUri: vscode.Uri,
url: string,
showOptions?: ShowOptions
) {
super();
this._webviewPanel = this._register(vscode.window.createWebviewPanel(SimpleBrowserView.viewType, SimpleBrowserView.title, {
): SimpleBrowserView {
const webview = vscode.window.createWebviewPanel(SimpleBrowserView.viewType, SimpleBrowserView.title, {
viewColumn: showOptions?.viewColumn ?? vscode.ViewColumn.Active,
preserveFocus: showOptions?.preserveFocus
}, {
enableScripts: true,
enableForms: true,
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.joinPath(extensionUri, 'media')
]
}));
});
return new SimpleBrowserView(extensionUri, url, webview);
}
public static restore(
extensionUri: vscode.Uri,
url: string,
webview: vscode.WebviewPanel,
): SimpleBrowserView {
return new SimpleBrowserView(extensionUri, url, webview);
}
private constructor(
private readonly extensionUri: vscode.Uri,
url: string,
webviewPanel: vscode.WebviewPanel,
) {
super();
this._webviewPanel = this._register(webviewPanel);
this._register(this._webviewPanel.webview.onDidReceiveMessage(e => {
switch (e.type) {
+4 -4
View File
@@ -12,10 +12,10 @@ vscode-codicons@^0.0.14:
resolved "https://registry.yarnpkg.com/vscode-codicons/-/vscode-codicons-0.0.14.tgz#e0d05418e2e195564ff6f6a2199d70415911c18f"
integrity sha512-6CEH5KT9ct5WMw7n5dlX7rB8ya4CUI2FSq1Wk36XaW+c5RglFtAanUV0T+gvZVVFhl/WxfjTvFHq06Hz9c1SLA==
vscode-extension-telemetry@0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
vscode-nls@^5.0.0:
version "5.0.0"
+2 -1
View File
@@ -4,6 +4,7 @@
},
"rules": {
// Disabled until the issues can be fixed
"@typescript-eslint/explicit-function-return-type": ["off"]
"@typescript-eslint/explicit-function-return-type": ["off"],
"@typescript-eslint/no-async-promise-executor": ["off"]
}
}
@@ -5,14 +5,15 @@
'use strict';
let path = require('path');
let fs = require('fs');
let https = require('https');
let url = require('url');
const path = require('path');
const fs = require('fs');
const https = require('https');
const url = require('url');
const minimatch = require('minimatch');
// list of languagesId not shipped with VSCode. The information is used to associate an icon with a language association
// Please try and keep this list in alphabetical order! Thank you.
let nonBuiltInLanguages = { // { fileNames, extensions }
const nonBuiltInLanguages = { // { fileNames, extensions }
"argdown": { extensions: ['ad', 'adown', 'argdown', 'argdn'] },
"bicep": { extensions: ['bicep'] },
"elixir": { extensions: ['ex'] },
@@ -41,13 +42,13 @@ let nonBuiltInLanguages = { // { fileNames, extensions }
};
// list of languagesId that inherit the icon from another language
let inheritIconFromLanguage = {
const inheritIconFromLanguage = {
"jsonc": 'json',
"postcss": 'css',
"django-html": 'html'
}
let FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo
const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo
let font, fontMappingsFile, fileAssociationFile, colorsFile;
if (!FROM_DISK) {
@@ -63,10 +64,10 @@ if (!FROM_DISK) {
}
function getCommitSha(repoId) {
let commitInfo = 'https://api.github.com/repos/' + repoId + '/commits/master';
const commitInfo = 'https://api.github.com/repos/' + repoId + '/commits/master';
return download(commitInfo).then(function (content) {
try {
let lastCommit = JSON.parse(content);
const lastCommit = JSON.parse(content);
return Promise.resolve({
commitSha: lastCommit.sha,
commitDate: lastCommit.commit.author.date
@@ -86,8 +87,8 @@ function download(source) {
return readFile(source);
}
return new Promise((c, e) => {
let _url = url.parse(source);
let options = { host: _url.host, port: _url.port, path: _url.path, headers: { 'User-Agent': 'NodeJS' } };
const _url = url.parse(source);
const options = { host: _url.host, port: _url.port, path: _url.path, headers: { 'User-Agent': 'NodeJS' } };
let content = '';
https.get(options, function (response) {
response.on('data', function (data) {
@@ -122,7 +123,7 @@ function downloadBinary(source, dest) {
https.get(source, function (response) {
switch (response.statusCode) {
case 200: {
let file = fs.createWriteStream(dest);
const file = fs.createWriteStream(dest);
response.on('data', function (chunk) {
file.write(chunk);
}).on('end', function () {
@@ -157,9 +158,9 @@ function copyFile(fileName, dest) {
cbCalled = true;
}
}
let rd = fs.createReadStream(fileName);
const rd = fs.createReadStream(fileName);
rd.on("error", handleError);
let wr = fs.createWriteStream(dest);
const wr = fs.createWriteStream(dest);
wr.on("error", handleError);
wr.on("close", function () {
if (!cbCalled) {
@@ -174,8 +175,8 @@ function copyFile(fileName, dest) {
function darkenColor(color) {
let res = '#';
for (let i = 1; i < 7; i += 2) {
let newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9);
let hex = newVal.toString(16);
const newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9);
const hex = newVal.toString(16);
if (hex.length === 1) {
res += '0';
}
@@ -195,28 +196,32 @@ function mergeMapping(to, from, property) {
}
function getLanguageMappings() {
let langMappings = {};
let allExtensions = fs.readdirSync('..');
const langMappings = {};
const allExtensions = fs.readdirSync('..');
for (let i = 0; i < allExtensions.length; i++) {
let dirPath = path.join('..', allExtensions[i], 'package.json');
const dirPath = path.join('..', allExtensions[i], 'package.json');
if (fs.existsSync(dirPath)) {
let content = fs.readFileSync(dirPath).toString();
let jsonContent = JSON.parse(content);
let languages = jsonContent.contributes && jsonContent.contributes.languages;
const content = fs.readFileSync(dirPath).toString();
const jsonContent = JSON.parse(content);
const languages = jsonContent.contributes && jsonContent.contributes.languages;
if (Array.isArray(languages)) {
for (let k = 0; k < languages.length; k++) {
let languageId = languages[k].id;
const languageId = languages[k].id;
if (languageId) {
let extensions = languages[k].extensions;
let mapping = {};
const extensions = languages[k].extensions;
const mapping = {};
if (Array.isArray(extensions)) {
mapping.extensions = extensions.map(function (e) { return e.substr(1).toLowerCase(); });
}
let filenames = languages[k].filenames;
const filenames = languages[k].filenames;
if (Array.isArray(filenames)) {
mapping.fileNames = filenames.map(function (f) { return f.toLowerCase(); });
}
let existing = langMappings[languageId];
const filenamePatterns = languages[k].filenamePatterns;
if (Array.isArray(filenamePatterns)) {
mapping.filenamePatterns = filenamePatterns.map(function (f) { return f.toLowerCase(); });
}
const existing = langMappings[languageId];
if (existing) {
// multiple contributions to the same language
@@ -224,10 +229,12 @@ function getLanguageMappings() {
if (languages[k].configuration) {
mergeMapping(mapping, existing, 'extensions');
mergeMapping(mapping, existing, 'fileNames');
mergeMapping(mapping, existing, 'filenamePatterns');
langMappings[languageId] = mapping;
} else {
mergeMapping(existing, mapping, 'extensions');
mergeMapping(existing, mapping, 'fileNames');
mergeMapping(existing, mapping, 'filenamePatterns');
}
} else {
langMappings[languageId] = mapping;
@@ -237,14 +244,12 @@ function getLanguageMappings() {
}
}
}
for (let languageId in nonBuiltInLanguages) {
for (const languageId in nonBuiltInLanguages) {
langMappings[languageId] = nonBuiltInLanguages[languageId];
}
return langMappings;
}
exports.copyFont = function () {
return downloadBinary(font, './icons/seti.woff');
};
@@ -252,27 +257,27 @@ exports.copyFont = function () {
exports.update = function () {
console.log('Reading from ' + fontMappingsFile);
let def2Content = {};
let ext2Def = {};
let fileName2Def = {};
let def2ColorId = {};
let colorId2Value = {};
let lang2Def = {};
const def2Content = {};
const ext2Def = {};
const fileName2Def = {};
const def2ColorId = {};
const colorId2Value = {};
const lang2Def = {};
function writeFileIconContent(info) {
let iconDefinitions = {};
let allDefs = Object.keys(def2Content).sort();
const iconDefinitions = {};
const allDefs = Object.keys(def2Content).sort();
for (let i = 0; i < allDefs.length; i++) {
let def = allDefs[i];
let entry = { fontCharacter: def2Content[def] };
let colorId = def2ColorId[def];
const def = allDefs[i];
const entry = { fontCharacter: def2Content[def] };
const colorId = def2ColorId[def];
if (colorId) {
let colorValue = colorId2Value[colorId];
const colorValue = colorId2Value[colorId];
if (colorValue) {
entry.fontColor = colorValue;
let entryInverse = { fontCharacter: entry.fontCharacter, fontColor: darkenColor(colorValue) };
const entryInverse = { fontCharacter: entry.fontCharacter, fontColor: darkenColor(colorValue) };
iconDefinitions[def + '_light'] = entryInverse;
}
}
@@ -280,9 +285,9 @@ exports.update = function () {
}
function getInvertSet(input) {
let result = {};
for (let assoc in input) {
let invertDef = input[assoc] + '_light';
const result = {};
for (const assoc in input) {
const invertDef = input[assoc] + '_light';
if (iconDefinitions[invertDef]) {
result[assoc] = invertDef;
}
@@ -290,7 +295,7 @@ exports.update = function () {
return result;
}
let res = {
const res = {
information_for_contributors: [
'This file has been generated from data in https://github.com/jesseweed/seti-ui',
'- icon definitions: https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti.less',
@@ -321,7 +326,7 @@ exports.update = function () {
version: 'https://github.com/jesseweed/seti-ui/commit/' + info.commitSha,
};
let path = './icons/vs-seti-icon-theme.json';
const path = './icons/vs-seti-icon-theme.json';
fs.writeFileSync(path, JSON.stringify(res, null, '\t'));
console.log('written ' + path);
}
@@ -330,18 +335,18 @@ exports.update = function () {
let match;
return download(fontMappingsFile).then(function (content) {
let regex = /@([\w-]+):\s*'(\\E[0-9A-F]+)';/g;
let contents = {};
const regex = /@([\w-]+):\s*'(\\E[0-9A-F]+)';/g;
const contents = {};
while ((match = regex.exec(content)) !== null) {
contents[match[1]] = match[2];
}
return download(fileAssociationFile).then(function (content) {
let regex2 = /\.icon-(?:set|partial)\(['"]([\w-\.+]+)['"],\s*['"]([\w-]+)['"],\s*(@[\w-]+)\)/g;
const regex2 = /\.icon-(?:set|partial)\(['"]([\w-\.+]+)['"],\s*['"]([\w-]+)['"],\s*(@[\w-]+)\)/g;
while ((match = regex2.exec(content)) !== null) {
let pattern = match[1];
const pattern = match[1];
let def = '_' + match[2];
let colorId = match[3];
const colorId = match[3];
let storedColorId = def2ColorId[def];
let i = 1;
while (storedColorId && colorId !== storedColorId) { // different colors for the same def?
@@ -364,20 +369,30 @@ exports.update = function () {
}
}
// replace extensions for languageId
let langMappings = getLanguageMappings();
const langMappings = getLanguageMappings();
for (let lang in langMappings) {
let mappings = langMappings[lang];
let exts = mappings.extensions || [];
let fileNames = mappings.fileNames || [];
const mappings = langMappings[lang];
const exts = mappings.extensions || [];
const fileNames = mappings.fileNames || [];
const filenamePatterns = mappings.filenamePatterns || [];
let preferredDef = null;
// use the first file association for the preferred definition
// use the first file extension association for the preferred definition
for (let i1 = 0; i1 < exts.length && !preferredDef; i1++) {
preferredDef = ext2Def[exts[i1]];
}
// use the first file association for the preferred definition
// use the first file name association for the preferred definition, if not availbale
for (let i1 = 0; i1 < fileNames.length && !preferredDef; i1++) {
preferredDef = fileName2Def[fileNames[i1]];
}
for (let i1 = 0; i1 < filenamePatterns.length && !preferredDef; i1++) {
let pattern = filenamePatterns[i1];
for (const name in fileName2Def) {
if (minimatch(name, pattern)) {
preferredDef = fileName2Def[name];
break;
}
}
}
if (preferredDef) {
lang2Def[lang] = preferredDef;
if (!nonBuiltInLanguages[lang] && !inheritIconFromLanguage[lang]) {
@@ -393,12 +408,21 @@ exports.update = function () {
delete fileName2Def[fileNames[i2]];
}
}
for (let i2 = 0; i2 < filenamePatterns.length; i2++) {
let pattern = filenamePatterns[i2];
// remove the filenamePatterns association, unless it is different from the preferred
for (const name in fileName2Def) {
if (minimatch(name, pattern) && fileName2Def[name] === preferredDef) {
delete fileName2Def[name];
}
}
}
}
}
}
for (let lang in inheritIconFromLanguage) {
let superLang = inheritIconFromLanguage[lang];
let def = lang2Def[superLang];
for (const lang in inheritIconFromLanguage) {
const superLang = inheritIconFromLanguage[lang];
const def = lang2Def[superLang];
if (def) {
lang2Def[lang] = def;
} else {
@@ -409,7 +433,7 @@ exports.update = function () {
return download(colorsFile).then(function (content) {
let regex3 = /(@[\w-]+):\s*(#[0-9a-z]+)/g;
const regex3 = /(@[\w-]+):\s*(#[0-9a-z]+)/g;
while ((match = regex3.exec(content)) !== null) {
colorId2Value[match[1]] = match[2];
}
@@ -417,9 +441,9 @@ exports.update = function () {
try {
writeFileIconContent(info);
let cgmanifestPath = './cgmanifest.json';
let cgmanifest = fs.readFileSync(cgmanifestPath).toString();
let cgmanifestContent = JSON.parse(cgmanifest);
const cgmanifestPath = './cgmanifest.json';
const cgmanifest = fs.readFileSync(cgmanifestPath).toString();
const cgmanifestContent = JSON.parse(cgmanifest);
cgmanifestContent['registrations'][0]['component']['git']['commitHash'] = info.commitSha;
fs.writeFileSync(cgmanifestPath, JSON.stringify(cgmanifestContent, null, '\t'));
console.log('updated ' + cgmanifestPath);
+1 -1
View File
@@ -6,7 +6,7 @@
"git": {
"name": "seti-ui",
"repositoryUrl": "https://github.com/jesseweed/seti-ui",
"commitHash": "b484fa778f564e71c81f22ee4c006c4664d66a47"
"commitHash": "8eacb11357be8b79868246f972702b6b2460e9ac"
}
},
"version": "0.1.0"
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -391,6 +391,11 @@
"editor.wordHighlightBackground": "#004454AA",
"editor.wordHighlightStrongBackground": "#005A6FAA",
// Editor: Colorized Bracket Highlighting
"editorBracketHighlight.foreground1": "#cdcdcdff",
"editorBracketHighlight.foreground2": "#b58900ff",
"editorBracketHighlight.foreground3": "#d33682ff",
// Editor: Suggest
// "editorSuggestWidget.background": "",
// "editorSuggestWidget.border": "",
+3 -2
View File
@@ -26,11 +26,12 @@
"strict": true,
"exactOptionalPropertyTypes": false,
"useUnknownInCatchVariables": false,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true
"forceConsistentCasingInFileNames": true
}
}
@@ -24,6 +24,7 @@ let outputChannel: vscode.OutputChannel;
export function activate(context: vscode.ExtensionContext) {
function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolvedAuthority> {
// eslint-disable-next-line no-async-promise-executor
const serverPromise = new Promise<vscode.ResolvedAuthority>(async (res, rej) => {
progress.report({ message: 'Starting Test Resolver' });
outputChannel = vscode.window.createOutputChannel('TestResolver');
@@ -129,7 +130,7 @@ export function activate(context: vscode.ExtensionContext) {
});
});
return serverPromise.then(serverAddr => {
return new Promise<vscode.ResolvedAuthority>(async (res, _rej) => {
return new Promise<vscode.ResolvedAuthority>((res, _rej) => {
const proxyServer = net.createServer(proxySocket => {
outputChannel.appendLine(`Proxy connection accepted`);
let remoteReady = true, localReady = true;
@@ -230,7 +231,27 @@ export function activate(context: vscode.ExtensionContext) {
}, (progress) => doResolve(_authority, progress));
},
tunnelFactory,
tunnelFeatures: { elevation: true, public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') },
tunnelFeatures: {
elevation: true,
public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts'),
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
{
id: 'public',
label: 'Public',
themeIcon: 'eye'
},
{
id: 'other',
label: 'Other',
themeIcon: 'circuit-board'
},
{
id: 'private',
label: 'Private',
themeIcon: 'eye-closed'
}
] : []
},
showCandidatePort
});
context.subscriptions.push(authorityResolverDisposable);
@@ -389,6 +410,7 @@ async function tunnelFactory(tunnelOptions: vscode.TunnelOptions, tunnelCreation
localAddress,
remoteAddress: tunnelOptions.remoteAddress,
public: !!vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') && tunnelOptions.public,
privacy: tunnelOptions.privacy,
protocol: tunnelOptions.protocol,
onDidDispose: onDidDispose.event,
dispose: () => {
@@ -25,6 +25,8 @@
{ "open": "(", "close": ")" },
{ "open": "<", "close": ">" }
],
"colorizedBracketPairs": [
],
"folding": {
"markers": {
"start": "^\\s*<!--\\s*#region\\b.*-->",
+2 -1
View File
@@ -39,7 +39,8 @@
".yml",
".eyaml",
".eyml",
".yaml"
".yaml",
".cff"
],
"firstLine": "^#cloud-config",
"configuration": "./language-configuration.json"
+7 -7
View File
@@ -15,19 +15,19 @@ cson-parser@^1.3.3:
coffee-script "^1.10.0"
esbuild@^0.11.12:
version "0.11.12"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.12.tgz#8cbe15bcb44212624c3e77c896a835f74dc71c3c"
integrity sha512-c8cso/1RwVj+fbDvLtUgSG4ZJQ0y9Zdrl6Ot/GAjyy4pdMCHaFnDMts5gqFnWRPLajWtEnI+3hlET4R9fVoZng==
version "0.11.23"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8"
integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==
fast-plist@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=
typescript@^4.4.1-rc:
version "4.4.1-rc"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.1-rc.tgz#f75f17985a2329a94fd911b3ef2e619c33636fa0"
integrity sha512-SYdeKrJiOajqNTI+sweR70JET43Z567HFNo7DvvBof8J5/bt2cywy7VoWXqZyrsHEmQ9foraLtLr30mcfpfz9w==
typescript@^4.8.0-dev.20220614:
version "4.8.0-dev.20220628"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220628.tgz#a8cbabf786f7e97b6da9d2bfd7e5839b96ef701b"
integrity sha512-89n1Fp/JQev/bPPuVferlfw0VguLxr0uFergLfPbhs+6Hlu3M5rLwplGLHL+JZ+0+BsTyWg779TmaMcqa5FB5Q==
vscode-grammar-updater@^1.0.3:
version "1.0.3"