Archived
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:
@@ -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);
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
];
|
||||
|
||||
const settings = getSettings();
|
||||
const isMac = settings.isMac;
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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, '"');
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
".gyp",
|
||||
".gypi",
|
||||
".pyi",
|
||||
".ipy"
|
||||
".ipy",
|
||||
".pyt"
|
||||
],
|
||||
"aliases": [
|
||||
"Python",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -6,6 +6,7 @@
|
||||
"enableProposedApi": true,
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"icon": "images/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.39.0"
|
||||
},
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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.*-->",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
".yml",
|
||||
".eyaml",
|
||||
".eyml",
|
||||
".yaml"
|
||||
".yaml",
|
||||
".cff"
|
||||
],
|
||||
"firstLine": "^#cloud-config",
|
||||
"configuration": "./language-configuration.json"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user