Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -37,7 +37,8 @@
"launch.json",
"tasks.json",
"keybindings.json",
"extensions.json"
"extensions.json",
"argv.json"
]
}
],
@@ -71,8 +72,8 @@
"url": "vscode://schemas/workspaceConfig"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/locale.json",
"url": "vscode://schemas/locale"
"fileMatch": "**/argv.json",
"url": "vscode://schemas/argv"
},
{
"fileMatch": "/.vscode/settings.json",
@@ -105,6 +106,10 @@
{
"fileMatch": "/.devcontainer.json",
"url": "./schemas/devContainer.schema.json"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/imageConfigs/*.json",
"url": "./schemas/attachContainer.schema.json"
}
]
},

View File

@@ -0,0 +1,36 @@
{
"$schema": "http://json-schema.org/schema#",
"description": "Configures an attached to container",
"allowComments": true,
"type": "object",
"definitions": {
"attachContainer": {
"type": "object",
"properties": {
"workspaceFolder": {
"type": "string",
"description": "The path of the workspace folder inside the container."
},
"forwardPorts": {
"type": "array",
"description": "Ports that are forwarded from the container to the local machine.",
"items": {
"type": "integer"
}
},
"extensions": {
"type": "array",
"description": "An array of extensions that should be installed into the container.",
"items": {
"type": "string"
}
}
}
}
},
"allOf": [
{
"$ref": "#/definitions/attachContainer"
}
]
}

View File

@@ -75,7 +75,8 @@ function registerVariableCompletions(pattern: string): vscode.Disposable {
{ label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") },
{ label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") },
{ label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") },
{ label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }
{ label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") },
{ label: 'defaultBuildTask', detail: localize('defaultBuildTask', "The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") },
].map(variable => ({
label: '${' + variable.label + '}',
range: new vscode.Range(startPosition, position),

View File

@@ -362,6 +362,11 @@
"title": "%command.ignore%",
"category": "Git"
},
{
"command": "git.revealInExplorer",
"title": "%command.revealInExplorer%",
"category": "Git"
},
{
"command": "git.stashIncludeUntracked",
"title": "%command.stashIncludeUntracked%",
@@ -507,6 +512,10 @@
"command": "git.restoreCommitTemplate",
"when": "false"
},
{
"command": "git.revealInExplorer",
"when": "false"
},
{
"command": "git.undoCommit",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
@@ -913,6 +922,11 @@
"when": "scmProvider == git && scmResourceGroup == merge",
"group": "inline"
},
{
"command": "git.revealInExplorer",
"when": "scmProvider == git && scmResourceGroup == merge",
"group": "2_view"
},
{
"command": "git.openFile2",
"when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction && config.git.openDiffOnClick",
@@ -948,6 +962,11 @@
"when": "scmProvider == git && scmResourceGroup == index",
"group": "inline"
},
{
"command": "git.revealInExplorer",
"when": "scmProvider == git && scmResourceGroup == index",
"group": "2_view"
},
{
"command": "git.openFile2",
"when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction && config.git.openDiffOnClick",
@@ -1007,6 +1026,11 @@
"command": "git.ignore",
"when": "scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification@3"
},
{
"command": "git.revealInExplorer",
"when": "scmProvider == git && scmResourceGroup == workingTree",
"group": "2_view"
}
],
"editor/title": [

View File

@@ -53,9 +53,10 @@
"command.removeRemote": "Remove Remote",
"command.sync": "Sync",
"command.syncRebase": "Sync (Rebase)",
"command.publish": "Publish Branch",
"command.publish": "Publish Branch...",
"command.showOutput": "Show Git Output",
"command.ignore": "Add to .gitignore",
"command.revealInExplorer": "Reveal in Explorer",
"command.stashIncludeUntracked": "Stash (Include Untracked)",
"command.stash": "Stash",
"command.stashPop": "Pop Stash...",

View File

@@ -28,8 +28,8 @@ function main(argv: string[]): void {
return fatal('Missing pipe');
}
if (process.env['VSCODE_GIT_COMMAND'] === 'fetch') {
return fatal('Skip fetch commands');
if (process.env['VSCODE_GIT_COMMAND'] === 'fetch' && !!process.env['VSCODE_GIT_FETCH_SILENT']) {
return fatal('Skip silent fetch commands');
}
const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string;

View File

@@ -100,7 +100,7 @@ export class AutoFetcher {
}
try {
await this.repository.fetchDefault();
await this.repository.fetchDefault({ silent: true });
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();

127
extensions/git/src/commands.ts Executable file → Normal file
View File

@@ -132,6 +132,20 @@ class HEADItem implements QuickPickItem {
get alwaysShow(): boolean { return true; }
}
class AddRemoteItem implements QuickPickItem {
constructor(private cc: CommandCenter) { }
get label(): string { return localize('add remote', '$(plus) Add a new remote...'); }
get description(): string { return ''; }
get alwaysShow(): boolean { return true; }
async run(repository: Repository): Promise<void> {
await this.cc.addRemote(repository);
}
}
interface CommandOptions {
repository?: boolean;
diff?: boolean;
@@ -493,7 +507,7 @@ export class CommandCenter {
const repositoryPath = await window.withProgress(
opts,
(_, token) => this.git.clone(url!, parentPath, token)
(progress, token) => this.git.clone(url!, parentPath, progress, token)
);
let message = localize('proposeopen', "Would you like to open the cloned repository?");
@@ -1249,11 +1263,13 @@ export class CommandCenter {
promptToSaveFilesBeforeCommit = 'never';
}
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
if (promptToSaveFilesBeforeCommit === 'staged') {
if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) {
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath));
}
@@ -1275,7 +1291,6 @@ export class CommandCenter {
}
}
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
@@ -1360,28 +1375,38 @@ export class CommandCenter {
private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
const message = repository.inputBox.value;
const getCommitMessage = async () => {
if (message) {
return message;
let _message: string | undefined = message;
if (!_message) {
let value: string | undefined = undefined;
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
value = (await repository.getCommit(repository.HEAD.commit)).message;
}
const branchName = repository.headShortName;
let placeHolder: string;
if (branchName) {
placeHolder = localize('commitMessageWithHeadLabel2', "Message (commit on '{0}')", branchName);
} else {
placeHolder = localize('commit message', "Commit message");
}
_message = await window.showInputBox({
value,
placeHolder,
prompt: localize('provide commit message', "Please provide a commit message"),
ignoreFocusOut: true
});
}
let value: string | undefined = undefined;
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
value = (await repository.getCommit(repository.HEAD.commit)).message;
}
return await window.showInputBox({
value,
placeHolder: localize('commit message', "Commit message"),
prompt: localize('provide commit message', "Please provide a commit message"),
ignoreFocusOut: true
});
return _message ? repository.cleanUpCommitEditMessage(_message) : _message;
};
const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
if (message && didCommit) {
repository.inputBox.value = await repository.getCommitTemplate();
repository.inputBox.value = await repository.getInputTemplate();
}
}
@@ -1458,6 +1483,15 @@ export class CommandCenter {
const commit = await repository.getCommit('HEAD');
if (commit.parents.length > 1) {
const yes = localize('undo commit', "Undo merge commit");
const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), yes);
if (result !== yes) {
return;
}
}
if (commit.parents.length > 0) {
await repository.reset('HEAD~');
} else {
@@ -1483,7 +1517,6 @@ export class CommandCenter {
const quickpick = window.createQuickPick();
quickpick.items = picks;
quickpick.placeholder = placeHolder;
quickpick.ignoreFocusOut = true;
quickpick.show();
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
@@ -1722,7 +1755,7 @@ export class CommandCenter {
const remoteRefs = repository.refs;
const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label));
const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[];
const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name! }));
const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from");
const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder });
@@ -1829,15 +1862,24 @@ export class CommandCenter {
}
} else {
const branchName = repository.HEAD.name;
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
const addRemote = new AddRemoteItem(this);
const picks = [...remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl })), addRemote];
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const pick = await window.showQuickPick(picks, { placeHolder });
const choice = await window.showQuickPick(picks, { placeHolder });
if (!pick) {
if (!choice) {
return;
}
await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (newRemote) {
await repository.pushTo(newRemote, branchName, undefined, forcePushMode);
}
} else {
await repository.pushTo(choice.label, branchName, undefined, forcePushMode);
}
}
}
@@ -1872,7 +1914,7 @@ export class CommandCenter {
}
@command('git.addRemote', { repository: true })
async addRemote(repository: Repository): Promise<void> {
async addRemote(repository: Repository): Promise<string | undefined> {
const remotes = repository.remotes;
const sanitize = (name: string) => {
@@ -1914,6 +1956,8 @@ export class CommandCenter {
}
await repository.addRemote(name, url);
return name;
}
@command('git.removeRemote', { repository: true })
@@ -2029,19 +2073,25 @@ export class CommandCenter {
return;
}
const addRemote = new AddRemoteItem(this);
const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote];
const branchName = repository.HEAD && repository.HEAD.name || '';
const selectRemote = async () => {
const picks = repository.remotes.map(r => r.name);
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
return await window.showQuickPick(picks, { placeHolder });
};
const choice = remotes.length === 1 ? remotes[0].name : await selectRemote();
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const choice = await window.showQuickPick(picks, { placeHolder });
if (!choice) {
return;
}
await repository.pushTo(choice, branchName, true);
if (choice === addRemote) {
const newRemote = await this.addRemote(repository);
if (newRemote) {
await repository.pushTo(newRemote, branchName, true);
}
} else {
await repository.pushTo(choice.label, branchName, true);
}
}
@command('git.ignore')
@@ -2069,6 +2119,19 @@ export class CommandCenter {
await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
}
@command('git.revealInExplorer')
async revealInExplorer(resourceState: SourceControlResourceState): Promise<void> {
if (!resourceState) {
return;
}
if (!(resourceState.resourceUri instanceof Uri)) {
return;
}
await commands.executeCommand('revealInExplorer', resourceState.resourceUri);
}
private async _stash(repository: Repository, includeUntracked = false): Promise<void> {
const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
const noStagedChanges = repository.indexGroup.resourceStates.length === 0;

View File

@@ -12,10 +12,12 @@ import { EventEmitter } from 'events';
import iconv = require('iconv-lite');
import * as filetype from 'file-type';
import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util';
import { CancellationToken } from 'vscode';
import { CancellationToken, Progress } from 'vscode';
import { URI } from 'vscode-uri';
import { detectEncoding } from './encoding';
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
import * as byline from 'byline';
import { StringDecoder } from 'string_decoder';
// https://github.com/microsoft/vscode/issues/65693
const MAX_CLI_LENGTH = 30000;
@@ -163,6 +165,7 @@ export interface SpawnOptions extends cp.SpawnOptions {
encoding?: string;
log?: boolean;
cancellationToken?: CancellationToken;
onSpawn?: (childProcess: cp.ChildProcess) => void;
}
async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise<IExecutionResult<Buffer>> {
@@ -341,7 +344,7 @@ export class Git {
return;
}
async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise<string> {
async clone(url: string, parentPath: string, progress: Progress<{ increment: number }>, cancellationToken?: CancellationToken): Promise<string> {
let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
let folderName = baseFolderName;
let folderPath = path.join(parentPath, folderName);
@@ -354,8 +357,36 @@ export class Git {
await mkdirp(parentPath);
const onSpawn = (child: cp.ChildProcess) => {
const decoder = new StringDecoder('utf8');
const lineStream = new byline.LineStream({ encoding: 'utf8' });
child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer)));
let totalProgress = 0;
let previousProgress = 0;
lineStream.on('data', (line: string) => {
let match: RegExpMatchArray | null = null;
if (match = /Counting objects:\s*(\d+)%/i.exec(line)) {
totalProgress = Math.floor(parseInt(match[1]) * 0.1);
} else if (match = /Compressing objects:\s*(\d+)%/i.exec(line)) {
totalProgress = 10 + Math.floor(parseInt(match[1]) * 0.1);
} else if (match = /Receiving objects:\s*(\d+)%/i.exec(line)) {
totalProgress = 20 + Math.floor(parseInt(match[1]) * 0.4);
} else if (match = /Resolving deltas:\s*(\d+)%/i.exec(line)) {
totalProgress = 60 + Math.floor(parseInt(match[1]) * 0.4);
}
if (totalProgress !== previousProgress) {
progress.report({ increment: totalProgress - previousProgress });
previousProgress = totalProgress;
}
});
};
try {
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath], { cancellationToken });
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'], { cancellationToken, onSpawn });
} catch (err) {
if (err.stderr) {
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
@@ -370,7 +401,8 @@ export class Git {
async getRepositoryRoot(repositoryPath: string): Promise<string> {
const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']);
return path.normalize(result.stdout.trim());
// Keep trailing spaces which are part of the directory name
return path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, ''));
}
async getRepositoryDotGit(repositoryPath: string): Promise<string> {
@@ -401,6 +433,10 @@ export class Git {
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
const child = this.spawn(args, options);
if (options.onSpawn) {
options.onSpawn(child);
}
if (options.input) {
child.stdin.end(options.input, 'utf8');
}
@@ -1366,8 +1402,9 @@ export class Repository {
await this.run(args);
}
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number } = {}): Promise<void> {
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise<void> {
const args = ['fetch'];
const spawnOptions: SpawnOptions = {};
if (options.remote) {
args.push(options.remote);
@@ -1387,8 +1424,12 @@ export class Repository {
args.push(`--depth=${options.depth}`);
}
if (options.silent) {
spawnOptions.env = { 'VSCODE_GIT_FETCH_SILENT': 'true' };
}
try {
await this.run(args);
await this.run(args, spawnOptions);
} catch (err) {
if (/No remote repository specified\./.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified;
@@ -1748,6 +1789,23 @@ export class Repository {
}
}
cleanupCommitEditMessage(message: string): string {
//TODO: Support core.commentChar
return message.replace(/^\s*#.*$\n?/gm, '').trim();
}
async getMergeMessage(): Promise<string | undefined> {
const mergeMsgPath = path.join(this.repositoryRoot, '.git', 'MERGE_MSG');
try {
const raw = await readfile(mergeMsgPath, 'utf8');
return raw.trim();
} catch {
return undefined;
}
}
async getCommitTemplate(): Promise<string> {
try {
const result = await this.run(['config', '--get', 'commit.template']);
@@ -1766,7 +1824,7 @@ export class Repository {
}
const raw = await readfile(templatePath, 'utf8');
return raw.replace(/^\s*#.*$\n?/gm, '');
return raw.trim();
} catch (err) {
return '';

View File

@@ -579,6 +579,27 @@ export class Repository implements Disposable {
return this._refs;
}
get headShortName(): string | undefined {
if (!this.HEAD) {
return;
}
const HEAD = this.HEAD;
if (HEAD.name) {
return HEAD.name;
}
const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
if (tagName) {
return tagName;
}
return (HEAD.commit || '').substr(0, 8);
}
private _remotes: Remote[] = [];
get remotes(): Remote[] {
return this._remotes;
@@ -729,8 +750,6 @@ export class Repository implements Disposable {
const onDidChangeCountBadge = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.countBadge', root));
onDidChangeCountBadge(this.setCountBadge, this, this.disposables);
this.setCountBadge();
this.updateCommitTemplate();
}
validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
@@ -806,12 +825,14 @@ export class Repository implements Disposable {
return toGitUri(uri, '', { replaceFileExtension: true });
}
private async updateCommitTemplate(): Promise<void> {
try {
this._sourceControl.commitTemplate = await this.repository.getCommitTemplate();
} catch (e) {
// noop
async getInputTemplate(): Promise<string> {
const mergeMessage = await this.repository.getMergeMessage();
if (mergeMessage) {
return mergeMessage;
}
return await this.repository.getCommitTemplate();
}
getConfigs(): Promise<{ key: string; value: string; }[]> {
@@ -1033,8 +1054,8 @@ export class Repository implements Disposable {
}
@throttle
async fetchDefault(): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch());
async fetchDefault(options: { silent?: boolean } = {}): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch(options));
}
@throttle
@@ -1236,6 +1257,10 @@ export class Repository implements Disposable {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}
async cleanUpCommitEditMessage(editMessage: string): Promise<string> {
return this.repository.cleanupCommitEditMessage(editMessage);
}
async ignore(files: Uri[]): Promise<void> {
return await this.run(Operation.Ignore, async () => {
const ignoreFile = `${this.repository.root}${path.sep}.gitignore`;
@@ -1457,9 +1482,9 @@ export class Repository implements Disposable {
const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]);
this._HEAD = HEAD;
this._refs = refs;
this._remotes = remotes;
this._submodules = submodules;
this._refs = refs!;
this._remotes = remotes!;
this._submodules = submodules!;
this.rebaseCommit = rebaseCommit;
const index: Resource[] = [];
@@ -1507,6 +1532,8 @@ export class Repository implements Disposable {
this.setCountBadge();
this._onDidChangeStatus.fire();
this._sourceControl.commitTemplate = await this.getInputTemplate();
}
private setCountBadge(): void {
@@ -1643,15 +1670,11 @@ export class Repository implements Disposable {
}
private updateInputBoxPlaceholder(): void {
const HEAD = this.HEAD;
if (HEAD) {
const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8);
const branchName = this.headShortName;
if (branchName) {
// '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay.
this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head);
this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", branchName);
} else {
this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)");
}

View File

@@ -160,7 +160,7 @@ export async function mkdirp(path: string, mode?: number): Promise<boolean> {
if (err.code === 'EEXIST') {
const stat = await nfcall<fs.Stats>(fs.stat, path);
if (stat.isDirectory) {
if (stat.isDirectory()) {
return;
}

View File

@@ -1,3 +1,17 @@
# Image Preview
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
## Features
This extension provides VS Code's built-in image preview functionality.
Supported image formats:
- `*.jpg`, `*.jpe`, `*.jpeg`
- `*.png`
- `*.bmp`
- `*.gif`
- `*.ico`
- `*.tga`
- `*.webp`

View File

@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
html, body {
width: 100%;
height: 100%;
max-height: 100%;
text-align: center;
}
body img {
@@ -77,22 +78,38 @@ body img {
margin-left: 5px;
}
.loading {
position: fixed;
.container.loading,
.container.error {
display: flex;
justify-content: center;
align-items: center;
}
.loading-indicator {
width: 30px;
height: 30px;
left: 50%;
top: 50%;
margin-top: -15px;
margin-left: -15px;
background-image: url('./loading.svg');
background-size: cover;
}
.vscode-dark .loading {
.loading-indicator,
.image-load-error-message {
display: none;
}
.loading .loading-indicator,
.error .image-load-error-message {
display: block;
}
.image-load-error-message {
margin: 1em;
}
.vscode-dark .loading-indicator {
background-image: url('./loading-dark.svg');
}
.vscode-high-contrast .loading {
.vscode-high-contrast .loading-indicator {
background-image: url('./loading-hc.svg');
}

View File

@@ -70,9 +70,10 @@
let ctrlPressed = false;
let altPressed = false;
let hasLoadedImage = false;
let consumeClick = false;
// Elements
const container = /** @type {HTMLElement} */(document.querySelector('body'));
const container = document.body;
const image = document.createElement('img');
function updateScale(newScale) {
@@ -88,9 +89,6 @@
image.style.width = 'auto';
vscode.setState(undefined);
} else {
const oldWidth = image.width;
const oldHeight = image.height;
scale = clamp(newScale, MIN_SCALE, MAX_SCALE);
if (scale >= PIXELATION_THRESHOLD) {
image.classList.add('pixelated');
@@ -98,25 +96,19 @@
image.classList.remove('pixelated');
}
const { scrollTop, scrollLeft } = image.parentElement;
const dx = (scrollLeft + image.parentElement.clientWidth / 2) / image.parentElement.scrollWidth;
const dy = (scrollTop + image.parentElement.clientHeight / 2) / image.parentElement.scrollHeight;
const dx = (window.scrollX + container.clientWidth / 2) / container.scrollWidth;
const dy = (window.scrollY + container.clientHeight / 2) / container.scrollHeight;
image.classList.remove('scale-to-fit');
image.style.minWidth = `${(image.naturalWidth * scale)}px`;
image.style.width = `${(image.naturalWidth * scale)}px`;
const newWidth = image.width;
const scaleFactor = (newWidth - oldWidth) / oldWidth;
const newScrollX = container.scrollWidth * dx - container.clientWidth / 2;
const newScrollY = container.scrollHeight * dy - container.clientHeight / 2;
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
const newScrollTop = ((oldHeight * scaleFactor * dy) + scrollTop);
// scrollbar.setScrollPosition({
// scrollLeft: newScrollLeft,
// scrollTop: newScrollTop,
// });
window.scrollTo(newScrollX, newScrollY);
vscode.setState({ scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop });
vscode.setState({ scale: scale, offsetX: newScrollX, offsetY: newScrollY });
}
vscode.postMessage({
@@ -125,6 +117,18 @@
});
}
function changeActive(value) {
if (value) {
container.classList.add('zoom-in');
consumeClick = true;
} else {
ctrlPressed = false;
altPressed = false;
container.classList.remove('zoom-out');
container.classList.remove('zoom-in');
}
}
function firstZoom() {
if (!image || !hasLoadedImage) {
return;
@@ -161,6 +165,18 @@
}
});
container.addEventListener('mousedown', (/** @type {MouseEvent} */ e) => {
if (!image || !hasLoadedImage) {
return;
}
if (e.button !== 0) {
return;
}
consumeClick = false;
});
container.addEventListener('click', (/** @type {MouseEvent} */ e) => {
if (!image || !hasLoadedImage) {
return;
@@ -170,6 +186,18 @@
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (isMac ? altPressed : ctrlPressed) {
container.classList.remove('zoom-in');
container.classList.add('zoom-out');
}
if (consumeClick) {
consumeClick = false;
return;
}
// left click
if (scale === 'fit') {
firstZoom();
@@ -227,24 +255,19 @@
});
container.classList.add('image');
container.classList.add('zoom-in');
image.classList.add('scale-to-fit');
image.addEventListener('load', () => {
document.querySelector('.loading').remove();
hasLoadedImage = true;
if (!image) {
return;
}
vscode.postMessage({
type: 'size',
value: `${image.naturalWidth}x${image.naturalHeight}`,
});
container.classList.add('ready');
document.body.classList.remove('loading');
document.body.classList.add('ready');
document.body.append(image);
updateScale(scale);
@@ -254,6 +277,12 @@
}
});
image.addEventListener('error', () => {
hasLoadedImage = true;
document.body.classList.add('error');
document.body.classList.remove('loading');
});
image.src = decodeURI(settings.src);
window.addEventListener('message', e => {
@@ -261,6 +290,9 @@
case 'setScale':
updateScale(e.data.scale);
break;
case 'setActive':
changeActive(e.data.value);
break;
}
});
}());

View File

@@ -24,9 +24,10 @@
{
"viewType": "imagePreview.previewEditor",
"displayName": "%webviewEditors.displayName%",
"priority": "builtin",
"selector": [
{
"filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,tif,tiff,webp}",
"filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,webp}",
"mime": "image/*"
}
]

View File

@@ -1,5 +1,5 @@
{
"displayName": "Image Preview",
"description": "Previews images.",
"description": "Provides VS Code's built-in image preview",
"webviewEditors.displayName": "Image Preview"
}

View File

@@ -4,19 +4,32 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
import * as nls from 'vscode-nls';
import { Disposable } from './dispose';
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry';
const localize = nls.loadMessageBundle();
const enum PreviewState {
Disposed,
Visible,
Active,
}
export class Preview extends Disposable {
public static readonly viewType = 'imagePreview.previewEditor';
private _active = true;
private readonly id: string = `${Date.now()}-${Math.random().toString()}`;
private _previewState = PreviewState.Visible;
private _imageSize: string | undefined;
private _imageZoom: Scale | undefined;
constructor(
private readonly extensionRoot: vscode.Uri,
resource: vscode.Uri,
private readonly resource: vscode.Uri,
private readonly webviewEditor: vscode.WebviewEditor,
private readonly sizeStatusBarEntry: SizeStatusBarEntry,
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
@@ -34,56 +47,91 @@ export class Preview extends Disposable {
]
};
webviewEditor.webview.html = this.getWebiewContents(webviewEditor, resource);
this._register(webviewEditor.webview.onDidReceiveMessage(message => {
switch (message.type) {
case 'size':
{
this.sizeStatusBarEntry.update(message.value);
this._imageSize = message.value;
this.update();
break;
}
case 'zoom':
{
this.zoomStatusBarEntry.update(message.value);
this._imageZoom = message.value;
this.update();
break;
}
}
}));
this._register(zoomStatusBarEntry.onDidChangeScale(e => {
this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
if (this._previewState === PreviewState.Active) {
this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale });
}
}));
this._register(webviewEditor.onDidChangeViewState(() => {
this.update();
}));
this._register(webviewEditor.onDidDispose(() => {
if (this._active) {
this.sizeStatusBarEntry.hide();
this.zoomStatusBarEntry.hide();
if (this._previewState === PreviewState.Active) {
this.sizeStatusBarEntry.hide(this.id);
this.zoomStatusBarEntry.hide(this.id);
}
this._previewState = PreviewState.Disposed;
}));
const watcher = this._register(vscode.workspace.createFileSystemWatcher(resource.fsPath));
this._register(watcher.onDidChange(e => {
if (e.toString() === this.resource.toString()) {
this.render();
}
}));
this._register(watcher.onDidDelete(e => {
if (e.toString() === this.resource.toString()) {
this.webviewEditor.dispose();
}
}));
this.render();
this.update();
}
private update() {
this._active = this.webviewEditor.active;
if (this._active) {
this.sizeStatusBarEntry.show();
this.zoomStatusBarEntry.show();
} else {
this.sizeStatusBarEntry.hide();
this.zoomStatusBarEntry.hide();
private render() {
if (this._previewState !== PreviewState.Disposed) {
this.webviewEditor.webview.html = this.getWebiewContents();
}
}
private getWebiewContents(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri): string {
private update() {
if (this._previewState === PreviewState.Disposed) {
return;
}
if (this.webviewEditor.active) {
this._previewState = PreviewState.Active;
this.sizeStatusBarEntry.show(this.id, this._imageSize || '');
this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit');
} else {
if (this._previewState === PreviewState.Active) {
this.sizeStatusBarEntry.hide(this.id);
this.zoomStatusBarEntry.hide(this.id);
}
this._previewState = PreviewState.Visible;
}
this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active });
}
private getWebiewContents(): string {
const version = Date.now().toString();
const settings = {
isMac: process.platform === 'darwin',
src: this.getResourcePath(webviewEditor, resource)
src: this.getResourcePath(this.webviewEditor, this.resource, version),
};
const nonce = Date.now().toString();
return /* html */`<!DOCTYPE html>
<html lang="en">
<head>
@@ -91,23 +139,33 @@ export class Preview extends Disposable {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Image Preview</title>
<link rel="stylesheet" class="code-user-style" href="${escapeAttribute(this.extensionResource('/media/main.css'))}" type="text/css" media="screen">
<link rel="stylesheet" href="${escapeAttribute(this.extensionResource('/media/main.css'))}" type="text/css" media="screen" nonce="${nonce}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data: ${this.webviewEditor.webview.cspSource}; script-src 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}';">
<meta id="image-preview-settings" data-settings="${escapeAttribute(JSON.stringify(settings))}">
</head>
<body class="container image scale-to-fit">
<div class='loading'></div>
<script src="${escapeAttribute(this.extensionResource('/media/main.js'))}"></script>
<body class="container image scale-to-fit loading">
<div class="loading-indicator"></div>
<div class="image-load-error-message">${localize('preview.imageLoadError', "An error occurred while loading the image")}</div>
<script src="${escapeAttribute(this.extensionResource('/media/main.js'))}" nonce="${nonce}"></script>
</body>
</html>`;
}
private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri) {
if (resource.scheme === 'data') {
return encodeURI(resource.toString(true));
}
private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri, version: string) {
switch (resource.scheme) {
case 'data':
return encodeURI(resource.toString(true));
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true));
case 'git':
// Show blank image
return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==');
default:
return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`);
}
}
private extensionResource(path: string) {

View File

@@ -5,29 +5,35 @@
import * as vscode from 'vscode';
import { Disposable } from './dispose';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class SizeStatusBarEntry extends Disposable {
private readonly _entry: vscode.StatusBarItem;
private _showingOwner: string | undefined;
constructor() {
super();
this._entry = this._register(vscode.window.createStatusBarItem({
id: 'imagePreview.size',
name: 'Image Size',
name: localize('sizeStatusBar.name', "Image Size"),
alignment: vscode.StatusBarAlignment.Right,
priority: 101 /* to the left of editor status (100) */,
}));
}
public show() {
public show(owner: string, text: string) {
this._showingOwner = owner;
this._entry.text = text;
this._entry.show();
}
public hide() {
this._entry.hide();
}
public update(text: string) {
this._entry.text = text;
public hide(owner: string) {
if (owner === this._showingOwner) {
this._entry.hide();
this._showingOwner = undefined;
}
}
}

View File

@@ -11,7 +11,7 @@ const localize = nls.loadMessageBundle();
const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel';
type Scale = number | 'fit';
export type Scale = number | 'fit';
export class ZoomStatusBarEntry extends Disposable {
private readonly _entry: vscode.StatusBarItem;
@@ -19,11 +19,13 @@ export class ZoomStatusBarEntry extends Disposable {
private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>());
public readonly onDidChangeScale = this._onDidChangeScale.event;
private _showOwner: string | undefined;
constructor() {
super();
this._entry = this._register(vscode.window.createStatusBarItem({
id: 'imagePreview.zoom',
name: 'Image Zoom',
name: localize('zoomStatusBar.name', "Image Zoom"),
alignment: vscode.StatusBarAlignment.Right,
priority: 102 /* to the left of editor size entry (101) */,
}));
@@ -48,16 +50,17 @@ export class ZoomStatusBarEntry extends Disposable {
this._entry.command = selectZoomLevelCommandId;
}
public show() {
public show(owner: string, scale: Scale) {
this._showOwner = owner;
this._entry.text = this.zoomLabel(scale);
this._entry.show();
}
public hide() {
this._entry.hide();
}
public update(scale: Scale) {
this._entry.text = this.zoomLabel(scale);
public hide(owner: string) {
if (owner === this._showOwner) {
this._entry.hide();
this._showOwner = undefined;
}
}
private zoomLabel(scale: Scale): string {

View File

@@ -44,7 +44,7 @@ The JSON language server has the following dependencies on the client's capabili
The client can send the following initialization options to the server:
- `provideFormatter: boolean | undefined`. If defined, the value defines wheter the server provides the `documentRangeFormattingProvider` capability on initialization. If undefined, the setting `json.format.enable` is used to determined wheter formatting is provided. The formatter will then be registered through dynamic registration. If the client does not support dynamic registration, no formatter will be available.
- `provideFormatter: boolean | undefined`. If defined, the value defines whether the server provides the `documentRangeFormattingProvider` capability on initialization. If undefined, the setting `json.format.enable` is used to determine whether formatting is provided. The formatter will then be registered through dynamic registration. If the client does not support dynamic registration, no formatter will be available.
- `handledSchemaProtocols`: The URI schemas handles by the server. See section `Schema configuration` below.
### Settings
@@ -60,7 +60,7 @@ The server supports the following settings:
- `format`
- `enable`: Whether the server should register the formatting support. This option is only applicable if the client supports *dynamicRegistration* for *rangeFormatting* and `initializationOptions.provideFormatter` is not defined.
- `schema`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content.
- `fileMatch`: an array or file names or paths (separated by `/`). `*` can be used as a wildcard.
- `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard.
- `url`: The URL of the schema, optional when also a schema is provided.
- `schema`: The schema content.
@@ -99,9 +99,9 @@ To find the schema for a given JSON document, the server uses the following mech
- The settings define a schema association based on the documents URL. Settings can either associate a schema URL to a file or path pattern, and they can directly provide a schema.
- Additionally, schema associations can also be provided by a custom 'schemaAssociations' configuration call.
Schemas are identified by URLs. To load the content of a schema, the JSON language server either tries to load from that URI or path itself, or delegates to the client.
Schemas are identified by URLs. To load the content of a schema, the JSON language server either tries to load from that URI or path itself or delegates to the client.
The `initializationOptions.handledSchemaProtocols` initialization option defines which URLs are handled by the server. Requests for all other URIs are send to the client.
The `initializationOptions.handledSchemaProtocols` initialization option defines which URLs are handled by the server. Requests for all other URIs are sent to the client.
`handledSchemaProtocols` is part of the initialization options and can't be changed while the server is running.
@@ -121,7 +121,7 @@ If `handledSchemaProtocols` is not set, the JSON language server will load the f
#### Schema content request
Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server specific, non-standardized, extension to the LSP.
Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server-specific, non-standardized, extension to the LSP.
Request:
- method: 'vscode/content'
@@ -130,12 +130,12 @@ Request:
#### Schema content change notification
When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server specific, non-standardized, extension to the LSP.
When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server-specific, non-standardized, extension to the LSP.
The server will, as a response, clear the schema content from the cache and reload the schema content when required again.
#### Schema associations notification
In addition to the settings, schemas associations can also be provided through a notification from the client to the server. This notification is a JSON language server specific, non-standardized, extension to the LSP.
In addition to the settings, schemas associations can also be provided through a notification from the client to the server. This notification is a JSON language server-specific, non-standardized, extension to the LSP.
Notification:
- method: 'json/schemaAssociations'

View File

@@ -1,7 +1,7 @@
{
"name": "vscode-json-languageserver",
"description": "JSON language server",
"version": "1.2.1",
"version": "1.2.2",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {

View File

@@ -144,7 +144,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
}
clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (params.initializationOptions.provideFormatter === undefined);
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean');
foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false);
const capabilities: ServerCapabilities = {
@@ -153,11 +153,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : undefined,
hoverProvider: true,
documentSymbolProvider: true,
documentRangeFormattingProvider: false,
documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
colorProvider: {},
foldingRangeProvider: true,
selectionRangeProvider: true,
documentFormattingProvider: params.initializationOptions.provideFormatter === true
selectionRangeProvider: true
};
return { capabilities };

View File

@@ -12,8 +12,7 @@
{ "open": "[", "close": "]", "notIn": ["string"] },
{ "open": "(", "close": ")", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "/*", "close": "*/", "notIn": ["string"] },
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
{ "open": "`", "close": "`", "notIn": ["string", "comment"] }
]
}
}

View File

@@ -27,7 +27,8 @@
".swcrc",
".webmanifest",
".js.map",
".css.map"
".css.map",
".har"
],
"filenames": [
"composer.lock",

View File

@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/00b05ebe6850083664d92d0eba6e5ee8f153baa6",
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/46724e2885f9557400ed91727d75c3574ceded3a",
"name": "Markdown",
"scopeName": "text.html.markdown",
"patterns": [
@@ -1682,6 +1682,39 @@
}
]
},
"fenced_code_block_log": {
"begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)(\\s+[^`~]*)?$)",
"name": "markup.fenced_code.block.markdown",
"end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
"beginCaptures": {
"3": {
"name": "punctuation.definition.markdown"
},
"4": {
"name": "fenced_code.block.language.markdown"
},
"5": {
"name": "fenced_code.block.language.attributes.markdown"
}
},
"endCaptures": {
"3": {
"name": "punctuation.definition.markdown"
}
},
"patterns": [
{
"begin": "(^|\\G)(\\s*)(.*)",
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
"contentName": "meta.embedded.block.log",
"patterns": [
{
"include": "text.log"
}
]
}
]
},
"fenced_code_block": {
"patterns": [
{
@@ -1831,6 +1864,9 @@
{
"include": "#fenced_code_block_markdown"
},
{
"include": "#fenced_code_block_log"
},
{
"include": "#fenced_code_block_unknown"
}

View File

@@ -123,26 +123,33 @@ Visual Studio-like style based on original C# coloring by Jason Diamond <jason@d
*/
.vscode-light .hljs-function,
.vscode-light .hljs-params {
.vscode-light .hljs-params,
.vscode-light .hljs-number,
.vscode-light .hljs-class {
color: inherit;
}
.vscode-light .hljs-comment,
.vscode-light .hljs-quote,
.vscode-light .hljs-number,
.vscode-light .hljs-class,
.vscode-light .hljs-variable {
color: #008000;
}
.vscode-light .hljs-keyword,
.vscode-light .hljs-selector-tag,
.vscode-light .hljs-built_in,
.vscode-light .hljs-name,
.vscode-light .hljs-tag {
color: #00f;
}
.vscode-light .hljs-built_in,
.vscode-light .hljs-builtin-name {
color: #007acc;
}
.vscode-light .hljs-string,
.vscode-light .hljs-title,
.vscode-light .hljs-section,
.vscode-light .hljs-attribute,
.vscode-light .hljs-literal,
@@ -154,10 +161,12 @@ Visual Studio-like style based on original C# coloring by Jason Diamond <jason@d
.vscode-light .hljs-selector-attr,
.vscode-light .hljs-selector-pseudo,
.vscode-light .hljs-meta {
.vscode-light .hljs-meta,
.vscode-light .hljs-meta-keyword {
color: #2b91af;
}
.vscode-light .hljs-title,
.vscode-light .hljs-doctag {
color: #808080;
}
@@ -179,4 +188,4 @@ Visual Studio-like style based on original C# coloring by Jason Diamond <jason@d
.vscode-light .hljs-strong {
font-weight: bold;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,350 +1,350 @@
{
"name": "markdown-language-features",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.0.0",
"icon": "icon.png",
"publisher": "vscode",
"enableProposedApi": true,
"license": "MIT",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.20.0"
},
"main": "./out/extension",
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:markdown",
"onCommand:markdown.preview.toggleLock",
"onCommand:markdown.preview.refresh",
"onCommand:markdown.showPreview",
"onCommand:markdown.showPreviewToSide",
"onCommand:markdown.showLockedPreviewToSide",
"onCommand:markdown.showSource",
"onCommand:markdown.showPreviewSecuritySelector",
"onCommand:markdown.api.render",
"name": "markdown-language-features",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.0.0",
"icon": "icon.png",
"publisher": "vscode",
"enableProposedApi": true,
"license": "MIT",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.20.0"
},
"main": "./out/extension",
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:markdown",
"onCommand:markdown.preview.toggleLock",
"onCommand:markdown.preview.refresh",
"onCommand:markdown.showPreview",
"onCommand:markdown.showPreviewToSide",
"onCommand:markdown.showLockedPreviewToSide",
"onCommand:markdown.showSource",
"onCommand:markdown.showPreviewSecuritySelector",
"onCommand:markdown.api.render",
"onCommand:notebook.showPreview",
"onWebviewPanel:markdown.preview"
],
"contributes": {
"commands": [
{
"command": "markdown.showPreview",
"title": "%markdown.preview.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-light.svg",
"dark": "./media/preview-dark.svg"
}
},
{
"command": "markdown.showPreviewToSide",
"title": "%markdown.previewSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-right-light.svg",
"dark": "./media/preview-right-dark.svg"
}
},
{
"command": "markdown.showLockedPreviewToSide",
"title": "%markdown.showLockedPreviewToSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-right-light.svg",
"dark": "./media/preview-right-dark.svg"
}
},
{
"command": "markdown.showSource",
"title": "%markdown.showSource.title%",
"category": "Markdown",
"icon": {
"light": "./media/view-source-light.svg",
"dark": "./media/view-source-dark.svg"
}
},
{
"command": "markdown.showPreviewSecuritySelector",
"title": "%markdown.showPreviewSecuritySelector.title%",
"category": "Markdown"
},
{
"command": "markdown.preview.refresh",
"title": "%markdown.preview.refresh.title%",
"category": "Markdown"
},
{
"command": "markdown.preview.toggleLock",
"title": "%markdown.preview.toggleLock.title%",
"category": "Markdown"
},
"onWebviewPanel:markdown.preview"
],
"contributes": {
"commands": [
{
"command": "markdown.showPreview",
"title": "%markdown.preview.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-light.svg",
"dark": "./media/preview-dark.svg"
}
},
{
"command": "markdown.showPreviewToSide",
"title": "%markdown.previewSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-right-light.svg",
"dark": "./media/preview-right-dark.svg"
}
},
{
"command": "markdown.showLockedPreviewToSide",
"title": "%markdown.showLockedPreviewToSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/preview-right-light.svg",
"dark": "./media/preview-right-dark.svg"
}
},
{
"command": "markdown.showSource",
"title": "%markdown.showSource.title%",
"category": "Markdown",
"icon": {
"light": "./media/view-source-light.svg",
"dark": "./media/view-source-dark.svg"
}
},
{
"command": "markdown.showPreviewSecuritySelector",
"title": "%markdown.showPreviewSecuritySelector.title%",
"category": "Markdown"
},
{
"command": "markdown.preview.refresh",
"title": "%markdown.preview.refresh.title%",
"category": "Markdown"
},
{
"command": "markdown.preview.toggleLock",
"title": "%markdown.preview.toggleLock.title%",
"category": "Markdown"
},
{
"command": "notebook.showPreview",
"title": "notebook.showPreview",
"category": "Notebook"
}
],
"menus": {
"editor/title": [
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"alt": "markdown.showPreview",
"group": "navigation"
},
{
"command": "markdown.showSource",
"when": "markdownPreviewFocus",
"group": "navigation"
},
{
"command": "markdown.preview.refresh",
"when": "markdownPreviewFocus",
"group": "1_markdown"
},
{
"command": "markdown.preview.toggleLock",
"when": "markdownPreviewFocus",
"group": "1_markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "markdownPreviewFocus",
"group": "1_markdown"
}
],
"explorer/context": [
{
"command": "markdown.showPreview",
"when": "resourceLangId == markdown",
"group": "navigation"
}
],
"editor/title/context": [
{
"command": "markdown.showPreview",
"when": "resourceLangId == markdown",
"group": "navigation"
}
],
"commandPalette": [
{
"command": "markdown.showPreview",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showLockedPreviewToSide",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showSource",
"when": "markdownPreviewFocus",
"group": "navigation"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "editorLangId == markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "markdownPreviewFocus"
},
{
"command": "markdown.preview.toggleLock",
"when": "markdownPreviewFocus"
},
{
"command": "markdown.preview.refresh",
"when": "editorLangId == markdown"
},
{
"command": "markdown.preview.refresh",
"when": "markdownPreviewFocus"
},
],
"menus": {
"editor/title": [
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"alt": "markdown.showPreview",
"group": "navigation"
},
{
"command": "markdown.showSource",
"when": "markdownPreviewFocus",
"group": "navigation"
},
{
"command": "markdown.preview.refresh",
"when": "markdownPreviewFocus",
"group": "1_markdown"
},
{
"command": "markdown.preview.toggleLock",
"when": "markdownPreviewFocus",
"group": "1_markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "markdownPreviewFocus",
"group": "1_markdown"
}
],
"explorer/context": [
{
"command": "markdown.showPreview",
"when": "resourceLangId == markdown",
"group": "navigation"
}
],
"editor/title/context": [
{
"command": "markdown.showPreview",
"when": "resourceLangId == markdown",
"group": "navigation"
}
],
"commandPalette": [
{
"command": "markdown.showPreview",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showLockedPreviewToSide",
"when": "editorLangId == markdown",
"group": "navigation"
},
{
"command": "markdown.showSource",
"when": "markdownPreviewFocus",
"group": "navigation"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "editorLangId == markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "markdownPreviewFocus"
},
{
"command": "markdown.preview.toggleLock",
"when": "markdownPreviewFocus"
},
{
"command": "markdown.preview.refresh",
"when": "editorLangId == markdown"
},
{
"command": "markdown.preview.refresh",
"when": "markdownPreviewFocus"
},
{
"command": "notebook.showPreview",
"when": "false"
}
]
},
"keybindings": [
{
"command": "markdown.showPreview",
"key": "shift+ctrl+v",
"mac": "shift+cmd+v",
"when": "editorLangId == markdown"
},
{
"command": "markdown.showPreviewToSide",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == markdown"
}
],
"configuration": {
"type": "object",
"title": "Markdown",
"order": 20,
"properties": {
"markdown.styles": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "%markdown.styles.dec%",
"scope": "resource"
},
"markdown.preview.breaks": {
"type": "boolean",
"default": false,
"description": "%markdown.preview.breaks.desc%",
"scope": "resource"
},
"markdown.preview.linkify": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.linkify%",
"scope": "resource"
},
"markdown.preview.fontFamily": {
"type": "string",
"default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif",
"description": "%markdown.preview.fontFamily.desc%",
"scope": "resource"
},
"markdown.preview.fontSize": {
"type": "number",
"default": 14,
"description": "%markdown.preview.fontSize.desc%",
"scope": "resource"
},
"markdown.preview.lineHeight": {
"type": "number",
"default": 1.6,
"description": "%markdown.preview.lineHeight.desc%",
"scope": "resource"
},
"markdown.preview.scrollPreviewWithEditor": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollPreviewWithEditor.desc%",
"scope": "resource"
},
"markdown.preview.markEditorSelection": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.markEditorSelection.desc%",
"scope": "resource"
},
"markdown.preview.scrollEditorWithPreview": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollEditorWithPreview.desc%",
"scope": "resource"
},
"markdown.preview.doubleClickToSwitchToEditor": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.doubleClickToSwitchToEditor.desc%",
"scope": "resource"
},
"markdown.preview.openMarkdownLinks": {
"type": "string",
"default": "inPreview",
"description": "%configuration.markdown.preview.openMarkdownLinks.description%",
"scope": "resource",
"enum": [
"inPreview",
"inEditor"
],
"enumDescriptions": [
"%configuration.markdown.preview.openMarkdownLinks.inPreview%",
"%configuration.markdown.preview.openMarkdownLinks.inEditor%"
]
},
"markdown.links.openLocation": {
"type": "string",
"default": "currentGroup",
"description": "%configuration.markdown.links.openLocation.description%",
"scope": "resource",
"enum": [
"currentGroup",
"beside"
],
"enumDescriptions": [
"%configuration.markdown.links.openLocation.currentGroup%",
"%configuration.markdown.links.openLocation.beside%"
]
},
"markdown.trace": {
"type": "string",
"enum": [
"off",
"verbose"
],
"default": "off",
"description": "%markdown.trace.desc%",
"scope": "window"
}
}
},
"configurationDefaults": {
"[markdown]": {
"editor.wordWrap": "on",
"editor.quickSuggestions": false
}
},
"jsonValidation": [
{
"fileMatch": "package.json",
"url": "./schemas/package.schema.json"
}
],
"markdown.previewStyles": [
"./media/markdown.css",
"./media/highlight.css"
],
"markdown.previewScripts": [
"./media/index.js"
]
},
"scripts": {
"compile": "gulp compile-extension:markdown-language-features && npm run build-preview",
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
"vscode:prepublish": "npm run build-ext && npm run build-preview",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
"build-preview": "webpack --mode development"
},
"dependencies": {
"highlight.js": "9.15.8",
"markdown-it": "^9.1.0",
"markdown-it-front-matter": "^0.1.2",
"vscode-extension-telemetry": "0.1.1",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/highlight.js": "9.12.3",
"@types/lodash.throttle": "^4.1.3",
"@types/markdown-it": "0.0.2",
"@types/node": "^10.14.8",
"lodash.throttle": "^4.1.1",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"ts-loader": "^4.0.1",
"typescript": "^3.3.1",
"vscode": "^1.1.10",
"webpack": "^4.1.0",
"webpack-cli": "^2.0.10"
}
]
},
"keybindings": [
{
"command": "markdown.showPreview",
"key": "shift+ctrl+v",
"mac": "shift+cmd+v",
"when": "editorLangId == markdown"
},
{
"command": "markdown.showPreviewToSide",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == markdown"
}
],
"configuration": {
"type": "object",
"title": "Markdown",
"order": 20,
"properties": {
"markdown.styles": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "%markdown.styles.dec%",
"scope": "resource"
},
"markdown.preview.breaks": {
"type": "boolean",
"default": false,
"description": "%markdown.preview.breaks.desc%",
"scope": "resource"
},
"markdown.preview.linkify": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.linkify%",
"scope": "resource"
},
"markdown.preview.fontFamily": {
"type": "string",
"default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif",
"description": "%markdown.preview.fontFamily.desc%",
"scope": "resource"
},
"markdown.preview.fontSize": {
"type": "number",
"default": 14,
"description": "%markdown.preview.fontSize.desc%",
"scope": "resource"
},
"markdown.preview.lineHeight": {
"type": "number",
"default": 1.6,
"description": "%markdown.preview.lineHeight.desc%",
"scope": "resource"
},
"markdown.preview.scrollPreviewWithEditor": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollPreviewWithEditor.desc%",
"scope": "resource"
},
"markdown.preview.markEditorSelection": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.markEditorSelection.desc%",
"scope": "resource"
},
"markdown.preview.scrollEditorWithPreview": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.scrollEditorWithPreview.desc%",
"scope": "resource"
},
"markdown.preview.doubleClickToSwitchToEditor": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.doubleClickToSwitchToEditor.desc%",
"scope": "resource"
},
"markdown.preview.openMarkdownLinks": {
"type": "string",
"default": "inPreview",
"description": "%configuration.markdown.preview.openMarkdownLinks.description%",
"scope": "resource",
"enum": [
"inPreview",
"inEditor"
],
"enumDescriptions": [
"%configuration.markdown.preview.openMarkdownLinks.inPreview%",
"%configuration.markdown.preview.openMarkdownLinks.inEditor%"
]
},
"markdown.links.openLocation": {
"type": "string",
"default": "currentGroup",
"description": "%configuration.markdown.links.openLocation.description%",
"scope": "resource",
"enum": [
"currentGroup",
"beside"
],
"enumDescriptions": [
"%configuration.markdown.links.openLocation.currentGroup%",
"%configuration.markdown.links.openLocation.beside%"
]
},
"markdown.trace": {
"type": "string",
"enum": [
"off",
"verbose"
],
"default": "off",
"description": "%markdown.trace.desc%",
"scope": "window"
}
}
},
"configurationDefaults": {
"[markdown]": {
"editor.wordWrap": "on",
"editor.quickSuggestions": false
}
},
"jsonValidation": [
{
"fileMatch": "package.json",
"url": "./schemas/package.schema.json"
}
],
"markdown.previewStyles": [
"./media/markdown.css",
"./media/highlight.css"
],
"markdown.previewScripts": [
"./media/index.js"
]
},
"scripts": {
"compile": "gulp compile-extension:markdown-language-features && npm run build-preview",
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
"vscode:prepublish": "npm run build-ext && npm run build-preview",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
"build-preview": "webpack --mode production"
},
"dependencies": {
"highlight.js": "9.15.10",
"markdown-it": "^10.0.0",
"markdown-it-front-matter": "^0.1.2",
"vscode-extension-telemetry": "0.1.1",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/highlight.js": "9.12.3",
"@types/lodash.throttle": "^4.1.3",
"@types/markdown-it": "0.0.2",
"@types/node": "^10.14.8",
"lodash.throttle": "^4.1.1",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"ts-loader": "^4.0.1",
"typescript": "^3.3.1",
"vscode": "^1.1.10",
"webpack": "^4.1.0",
"webpack-cli": "^2.0.10"
}
}

View File

@@ -19,7 +19,7 @@ const settings = getSettings();
const vscode = acquireVsCodeApi();
// Set VS Code state
let state = getData<{ line: number, fragment: string }>('data-state');
let state = getData<{ line: number; fragment: string; }>('data-state');
vscode.setState(state);
const messaging = createPosterForVsCode(vscode);
@@ -67,7 +67,7 @@ const onUpdateView = (() => {
})();
let updateImageSizes = throttle(() => {
const imageInfo: { id: string, height: number, width: number }[] = [];
const imageInfo: { id: string, height: number, width: number; }[] = [];
let images = document.getElementsByTagName('img');
if (images) {
let i;
@@ -129,6 +129,8 @@ document.addEventListener('dblclick', event => {
}
});
const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders'];
document.addEventListener('click', event => {
if (!event) {
return;
@@ -138,36 +140,40 @@ document.addEventListener('click', event => {
while (node) {
if (node.tagName && node.tagName === 'A' && node.href) {
if (node.getAttribute('href').startsWith('#')) {
break;
return;
}
if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) {
const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#');
messaging.postMessage('clickLink', { path, fragment });
// Pass through known schemes
if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) {
return;
}
const hrefText = node.getAttribute('data-href') || node.getAttribute('href');
// If original link doesn't look like a url, delegate back to VS Code to resolve
if (!/^[a-z\-]+:/i.test(hrefText)) {
messaging.postMessage('openLink', { href: hrefText });
event.preventDefault();
event.stopPropagation();
break;
return;
}
break;
return;
}
node = node.parentNode;
}
}, true);
if (settings.scrollEditorWithPreview) {
window.addEventListener('scroll', throttle(() => {
if (scrollDisabled) {
scrollDisabled = false;
} else {
const line = getEditorLineNumberForPageOffset(window.scrollY);
if (typeof line === 'number' && !isNaN(line)) {
messaging.postMessage('revealLine', { line });
state.line = line;
vscode.setState(state);
}
window.addEventListener('scroll', throttle(() => {
if (scrollDisabled) {
scrollDisabled = false;
} else {
const line = getEditorLineNumberForPageOffset(window.scrollY);
if (typeof line === 'number' && !isNaN(line)) {
messaging.postMessage('revealLine', { line });
state.line = line;
vscode.setState(state);
}
}, 50));
}
}
}, 50));
function escapeRegExp(text: string) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

View File

@@ -56,7 +56,7 @@ function registerMarkdownLanguageFeatures(
return vscode.Disposable.from(
vscode.languages.setLanguageConfiguration('markdown', {
wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number})+', 'ug'),
wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})+', 'ug'),
}),
vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()),

View File

@@ -25,7 +25,7 @@ interface WebviewMessage {
interface CacheImageSizesMessage extends WebviewMessage {
readonly type: 'cacheImageSizes';
readonly body: { id: string, width: number, height: number }[];
readonly body: { id: string, width: number, height: number; }[];
}
interface RevealLineMessage extends WebviewMessage {
@@ -43,10 +43,9 @@ interface DidClickMessage extends WebviewMessage {
}
interface ClickLinkMessage extends WebviewMessage {
readonly type: 'clickLink';
readonly type: 'openLink';
readonly body: {
readonly path: string;
readonly fragment?: string;
readonly href: string;
};
}
@@ -88,7 +87,7 @@ export class MarkdownPreview extends Disposable {
private forceUpdate = false;
private isScrolling = false;
private _disposed: boolean = false;
private imageInfo: { id: string, width: number, height: number }[] = [];
private imageInfo: { id: string, width: number, height: number; }[] = [];
private scrollToFragment: string | undefined;
public static async revive(
@@ -202,8 +201,8 @@ export class MarkdownPreview extends Disposable {
this.onDidClickPreview(e.body.line);
break;
case 'clickLink':
this.onDidClickPreviewLink(e.body.path, e.body.fragment);
case 'openLink':
this.onDidClickPreviewLink(e.body.href);
break;
case 'showPreviewSecuritySelector':
@@ -284,15 +283,17 @@ export class MarkdownPreview extends Disposable {
super.dispose();
}
public update(resource: vscode.Uri) {
const editor = vscode.window.activeTextEditor;
public update(resource: vscode.Uri, isRefresh = true) {
// Reposition scroll preview, position scroll to the top if active text editor
// doesn't corresponds with preview
const editor = vscode.window.activeTextEditor;
if (editor) {
if (editor.document.uri.fsPath === resource.fsPath) {
this.line = getVisibleLine(editor);
} else {
this.line = 0;
if (!isRefresh || this._previewConfigurations.loadAndCacheConfiguration(this._resource).scrollEditorWithPreview) {
if (editor.document.uri.fsPath === resource.fsPath) {
this.line = getVisibleLine(editor);
} else {
this.line = 0;
}
}
}
@@ -320,7 +321,7 @@ export class MarkdownPreview extends Disposable {
public refresh() {
this.forceUpdate = true;
this.update(this._resource);
this.update(this._resource, true);
}
public updateConfiguration() {
@@ -484,6 +485,12 @@ export class MarkdownPreview extends Disposable {
private onDidScrollPreview(line: number) {
this.line = line;
const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource);
if (!config.scrollEditorWithPreview) {
return;
}
for (const editor of vscode.window.visibleTextEditors) {
if (!this.isPreviewOf(editor.document.uri)) {
continue;
@@ -528,12 +535,19 @@ export class MarkdownPreview extends Disposable {
this.editor.webview.html = html;
}
private async onDidClickPreviewLink(path: string, fragment: string | undefined) {
this.scrollToFragment = undefined;
private async onDidClickPreviewLink(href: string) {
let [hrefPath, fragment] = decodeURIComponent(href).split('#');
// We perviously already resolve absolute paths.
// Now make sure we handle relative file paths
if (hrefPath[0] !== '/') {
hrefPath = path.join(path.dirname(this.resource.path), hrefPath);
}
const config = vscode.workspace.getConfiguration('markdown', this.resource);
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
if (openLinks === 'inPreview') {
const markdownLink = await resolveLinkToMarkdownFile(path);
const markdownLink = await resolveLinkToMarkdownFile(hrefPath);
if (markdownLink) {
if (fragment) {
this.scrollToFragment = fragment;
@@ -543,10 +557,10 @@ export class MarkdownPreview extends Disposable {
}
}
vscode.commands.executeCommand('_markdown.openDocumentLink', { path, fragment, fromResource: this.resource });
vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource });
}
private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number }[]) {
private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number; }[]) {
this.imageInfo = imageInfo;
}
}

View File

@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as crypto from 'crypto';
import { MarkdownIt, Token } from 'markdown-it';
import * as path from 'path';
import { MarkdownIt, Token } from 'markdown-it';
import * as vscode from 'vscode';
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
import { SkinnyTextDocument } from './tableOfContentsProvider';
import { getUriForLinkWithKnownExternalScheme } from './util/links';
import { Schemes, isOfScheme } from './util/links';
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
@@ -105,10 +105,10 @@ export class MarkdownEngine {
this.addImageStabilizer(md);
this.addFencedRenderer(md);
this.addLinkNormalizer(md);
this.addLinkValidator(md);
this.addNamedHeaders(md);
this.addLinkRenderer(md);
return md;
});
}
@@ -150,6 +150,7 @@ export class MarkdownEngine {
public async render(input: SkinnyTextDocument | string): Promise<string> {
const config = this.getConfig(typeof input === 'string' ? undefined : input.uri);
const engine = await this.getEngine(config);
const tokens = typeof input === 'string'
? this.tokenizeString(input, engine)
: this.tokenizeDocument(input, config, engine);
@@ -233,36 +234,28 @@ export class MarkdownEngine {
const normalizeLink = md.normalizeLink;
md.normalizeLink = (link: string) => {
try {
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
if (externalSchemeUri) {
// set true to skip encoding
return normalizeLink(externalSchemeUri.toString(true));
}
// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
if (!/^[a-z\-]+:/i.test(link)) {
// Use a fake scheme for parsing
let uri = vscode.Uri.parse('markdown-link:' + link);
// Assume it must be an relative or absolute file path
// Use a fake scheme to avoid parse warnings
let uri = vscode.Uri.parse(`vscode-resource:${link}`);
if (uri.path) {
// Assume it must be a file
const fragment = uri.fragment;
// Relative paths should be resolved correctly inside the preview but we need to
// handle absolute paths specially (for images) to resolve them relative to the workspace root
if (uri.path[0] === '/') {
const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!);
if (root) {
uri = vscode.Uri.file(path.join(root.uri.fsPath, uri.path));
uri = uri.with({
path: path.join(root.uri.fsPath, uri.path),
});
}
} else {
uri = vscode.Uri.file(path.join(path.dirname(this.currentDocument!.path), uri.path));
}
if (fragment) {
if (uri.fragment) {
uri = uri.with({
fragment: this.slugifier.fromHeading(fragment).value
fragment: this.slugifier.fromHeading(uri.fragment).value
});
}
return normalizeLink(uri.with({ scheme: 'vscode-resource' }).toString(true));
} else if (!uri.path && uri.fragment) {
return `#${this.slugifier.fromHeading(uri.fragment).value}`;
return normalizeLink(uri.toString(true).replace(/^markdown-link:/, ''));
}
} catch (e) {
// noop
@@ -275,7 +268,7 @@ export class MarkdownEngine {
const validateLink = md.validateLink;
md.validateLink = (link: string) => {
// support file:// links
return validateLink(link) || link.startsWith('file:') || /^data:image\/.*?;/.test(link);
return validateLink(link) || isOfScheme(Schemes.file, link) || /^data:image\/.*?;/.test(link);
};
}
@@ -303,6 +296,22 @@ export class MarkdownEngine {
}
};
}
private addLinkRenderer(md: any): void {
const old_render = md.renderer.rules.link_open || ((tokens: any, idx: number, options: any, _env: any, self: any) => {
return self.renderToken(tokens, idx, options);
});
md.renderer.rules.link_open = (tokens: any, idx: number, options: any, env: any, self: any) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const href = token.attrs[hrefIndex][1];
token.attrPush(['data-href', href]);
}
return old_render(tokens, idx, options, env, self);
};
}
}
async function getMarkdownOptions(md: () => MarkdownIt) {
@@ -310,16 +319,7 @@ async function getMarkdownOptions(md: () => MarkdownIt) {
return {
html: true,
highlight: (str: string, lang?: string) => {
// Workaround for highlight not supporting tsx: https://github.com/isagalaev/highlight.js/issues/1155
if (lang && ['tsx', 'typescriptreact'].includes(lang.toLocaleLowerCase())) {
lang = 'jsx';
}
if (lang && lang.toLocaleLowerCase() === 'json5') {
lang = 'json';
}
if (lang && ['c#', 'csharp'].includes(lang.toLocaleLowerCase())) {
lang = 'cs';
}
lang = normalizeHighlightLang(lang);
if (lang && hljs.getLanguage(lang)) {
try {
return `<div>${hljs.highlight(lang, str, true).value}</div>`;
@@ -330,3 +330,24 @@ async function getMarkdownOptions(md: () => MarkdownIt) {
}
};
}
function normalizeHighlightLang(lang: string | undefined) {
switch (lang && lang.toLowerCase()) {
case 'tsx':
case 'typescriptreact':
// Workaround for highlight not supporting tsx: https://github.com/isagalaev/highlight.js/issues/1155
return 'jsx';
case 'json5':
case 'jsonc':
return 'json';
case 'c#':
case 'csharp':
return 'cs';
default:
return lang;
}
}

View File

@@ -5,14 +5,30 @@
import * as vscode from 'vscode';
const knownSchemes = ['http:', 'https:', 'file:', 'mailto:', 'data:', `${vscode.env.uriScheme}:`, 'vscode:', 'vscode-insiders:', 'vscode-resource:'];
export const Schemes = {
http: 'http:',
https: 'https:',
file: 'file:',
mailto: 'mailto:',
data: 'data:',
vscode: 'vscode:',
'vscode-insiders': 'vscode-insiders:',
'vscode-resource': 'vscode-resource',
};
export function getUriForLinkWithKnownExternalScheme(
link: string,
): vscode.Uri | undefined {
if (knownSchemes.some(knownScheme => link.toLowerCase().startsWith(knownScheme))) {
const knownSchemes = [
...Object.values(Schemes),
`${vscode.env.uriScheme}:`
];
export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | undefined {
if (knownSchemes.some(knownScheme => isOfScheme(knownScheme, link))) {
return vscode.Uri.parse(link);
}
return undefined;
}
export function isOfScheme(scheme: string, link: string): boolean {
return link.toLowerCase().startsWith(scheme);
}

View File

@@ -1973,10 +1973,10 @@ enhanced-resolve@^4.0.0:
memory-fs "^0.4.0"
tapable "^1.0.0"
entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA=
entities@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
@@ -2933,10 +2933,10 @@ he@1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
highlight.js@9.15.8:
version "9.15.8"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971"
integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA==
highlight.js@9.15.10:
version "9.15.10"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
hmac-drbg@^1.0.0:
version "1.0.1"
@@ -3900,13 +3900,13 @@ markdown-it-front-matter@^0.1.2:
resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.1.2.tgz#e50bf56e77e6a4f5ac4ffa894d4d45ccd9896b20"
integrity sha1-5Qv1bnfmpPWsT/qJTU1FzNmJayA=
markdown-it@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.1.0.tgz#df9601c168568704d554b1fff9af0c5b561168d9"
integrity sha512-xHKG4C8iPriyfu/jc2hsCC045fKrMQ0VexX2F1FGYiRxDxqMB2aAhF8WauJ3fltn2kb90moGBkiiEdooGIg55w==
markdown-it@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
dependencies:
argparse "^1.0.7"
entities "~1.1.1"
entities "~2.0.0"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"

View File

@@ -3,14 +3,12 @@
"tokenColors": [
{
"settings": {
"background": "#000c18",
"foreground": "#6688cc"
}
},
{
"scope": ["meta.embedded", "source.groovy.embedded"],
"settings": {
"background": "#000c18",
"foreground": "#6688cc"
}
},
@@ -173,7 +171,6 @@
"name": "Invalid",
"scope": "invalid",
"settings": {
"background": "#F92672",
"fontStyle": "",
"foreground": "#F8F8F0"
}
@@ -182,7 +179,6 @@
"name": "Invalid deprecated",
"scope": "invalid.deprecated",
"settings": {
"background": "#AE81FF",
"foreground": "#F8F8F0"
}
},
@@ -193,7 +189,6 @@
"meta.diff.header"
],
"settings": {
"background": "#b58900",
"fontStyle": "italic",
"foreground": "#E0EDDD"
}
@@ -202,7 +197,6 @@
"name": "diff: deleted",
"scope": "markup.deleted",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#dc322f"
}
@@ -211,7 +205,6 @@
"name": "diff: changed",
"scope": "markup.changed",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#cb4b16"
}
@@ -220,7 +213,6 @@
"name": "diff: inserted",
"scope": "markup.inserted",
"settings": {
"background": "#eee8d5",
"foreground": "#219186"
}
},
@@ -378,7 +370,6 @@
"tab.border": "#2b2b4a",
// "tab.activeBackground": "",
"tab.inactiveBackground": "#10192c",
"tab.modifiedBorder": "#0072bf",
// "tab.activeForeground": "",
// "tab.inactiveForeground": "",

View File

@@ -23,7 +23,6 @@
"editorGroupHeader.tabsBackground": "#131510",
"editorLineNumber.activeForeground": "#adadad",
"tab.inactiveBackground": "#131510",
"tab.modifiedBorder": "#cd9731",
"titleBar.activeBackground": "#423523",
"statusBar.background": "#423523",
"statusBar.debuggingBackground": "#423523",
@@ -55,14 +54,12 @@
"tokenColors": [
{
"settings": {
"background": "#221a0f",
"foreground": "#d3af86"
}
},
{
"scope": ["meta.embedded", "source.groovy.embedded"],
"settings": {
"background": "#221a0f",
"foreground": "#d3af86"
}
},
@@ -335,7 +332,6 @@
"name": "Separator",
"scope": "meta.separator",
"settings": {
"background": "#84613d",
"foreground": "#d3af86"
}
},

View File

@@ -24,7 +24,6 @@
"editorGroupHeader.tabsBackground": "#282828",
"tab.inactiveBackground": "#404040",
"tab.border": "#303030",
"tab.modifiedBorder": "#6796e6",
"tab.inactiveForeground": "#d8d8d8",
"peekView.border": "#3655b5",
"panelTitle.activeForeground": "#ffffff",
@@ -48,24 +47,15 @@
"tokenColors": [
{
"settings": {
"background": "#1e1e1e",
"foreground": "#C5C8C6"
}
},
{
"name": "By uonick",
"settings": {
"background": "#202025ff",
"foreground": "#c5c8c6ff"
}
},
{
"scope": [
"meta.embedded",
"source.groovy.embedded"
],
"settings": {
"background": "#1e1e1e",
"foreground": "#C5C8C6"
}
},
@@ -115,7 +105,6 @@
"settings": {
"fontStyle": "",
"foreground": "#8080FF",
"background": "#1e1e1e"
}
},
{
@@ -148,7 +137,6 @@
"settings": {
"fontStyle": "",
"foreground": "#9B0000",
"background": "#1E1E1E"
}
},
{
@@ -482,7 +470,6 @@
"name": "diff: header",
"scope": "meta.diff, meta.diff.header",
"settings": {
"background": "#b58900",
"fontStyle": "italic",
"foreground": "#E0EDDD"
}
@@ -491,7 +478,6 @@
"name": "diff: deleted",
"scope": "markup.deleted",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#dc322f"
}
@@ -500,7 +486,6 @@
"name": "diff: changed",
"scope": "markup.changed",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#cb4b16"
}
@@ -509,7 +494,6 @@
"name": "diff: inserted",
"scope": "markup.inserted",
"settings": {
"background": "#eee8d5",
"foreground": "#219186"
}
},

View File

@@ -36,7 +36,6 @@
"editorGroup.dropBackground": "#41433980",
"tab.inactiveBackground": "#34352f",
"tab.border": "#1e1f1c",
"tab.modifiedBorder": "#007acc",
"tab.inactiveForeground": "#ccccc7", // needs to be bright so it's readable when another editor group is focused
"widget.shadow": "#000000",
"progressBar.background": "#75715E",
@@ -65,8 +64,8 @@
"focusBorder": "#75715E",
"editorWidget.background": "#1e1f1c",
"debugToolBar.background": "#1e1f1c",
"diffEditor.insertedTextBackground": "#66852880", // middle of #272822 and #a6e22e
"diffEditor.removedTextBackground": "#90274A80", // middle of #272822 and #f92672
"diffEditor.insertedTextBackground": "#4b661680", // middle of #272822 and #a6e22e
"diffEditor.removedTextBackground": "#90274A70", // middle of #272822 and #f92672
"inputValidation.errorBackground": "#90274A", // middle of #272822 and #f92672
"inputValidation.errorBorder": "#f92672",
"inputValidation.warningBackground": "#848528", // middle of #272822 and #e2e22e
@@ -105,7 +104,6 @@
"tokenColors": [
{
"settings": {
"background": "#272822",
"foreground": "#F8F8F2"
}
},
@@ -115,7 +113,6 @@
"source.groovy.embedded"
],
"settings": {
"background": "#272822",
"foreground": "#F8F8F2"
}
},
@@ -123,7 +120,7 @@
"name": "Comment",
"scope": "comment",
"settings": {
"foreground": "#75715E"
"foreground": "#88846f"
}
},
{
@@ -287,7 +284,6 @@
"name": "Invalid",
"scope": "invalid",
"settings": {
"background": "#F92672",
"fontStyle": "",
"foreground": "#F8F8F0"
}
@@ -296,7 +292,6 @@
"name": "Invalid deprecated",
"scope": "invalid.deprecated",
"settings": {
"background": "#AE81FF",
"foreground": "#F8F8F0"
}
},

View File

@@ -3,7 +3,6 @@
"tokenColors": [
{
"settings": {
"background": "#F5F5F5",
"foreground": "#333333"
}
},
@@ -13,7 +12,6 @@
"source.groovy.embedded"
],
"settings": {
"background": "#F5F5F5",
"foreground": "#333333"
}
},
@@ -47,18 +45,10 @@
"foreground": "#448C27"
}
},
{
"name": "Invalid - Deprecated",
"scope": "invalid.deprecated",
"settings": {
"background": "#96000014"
}
},
{
"name": "Invalid - Illegal",
"scope": "invalid.illegal",
"settings": {
"background": "#96000014",
"foreground": "#660000"
}
},
@@ -195,20 +185,6 @@
"foreground": "#777777"
}
},
{
"name": "Embedded Source",
"scope": [
"string source",
"text source"
],
"settings": {
"background": "#EAEBE6"
}
},
{
"name": "-----------------------------------",
"settings": {}
},
{
"name": "HTML: Doctype Declaration",
"scope": [
@@ -261,10 +237,6 @@
"foreground": "#9C5D27"
}
},
{
"name": "-----------------------------------",
"settings": {}
},
{
"name": "CSS: Selectors",
"scope": [
@@ -305,15 +277,10 @@
"fontStyle": "bold"
}
},
{
"name": "-----------------------------------",
"settings": {}
},
{
"name": "Markup: Changed",
"scope": "markup.changed",
"settings": {
"background": "#FFFFDD",
"foreground": "#000000"
}
},
@@ -321,7 +288,6 @@
"name": "Markup: Deletion",
"scope": "markup.deleted",
"settings": {
"background": "#FFDDDD",
"foreground": "#000000"
}
},
@@ -336,7 +302,6 @@
"name": "Markup: Error",
"scope": "markup.error",
"settings": {
"background": "#96000014",
"foreground": "#660000"
}
},
@@ -344,7 +309,6 @@
"name": "Markup: Insertion",
"scope": "markup.inserted",
"settings": {
"background": "#DDFFDD",
"foreground": "#000000"
}
},
@@ -432,10 +396,6 @@
"foreground": "#9C5D27"
}
},
{
"name": "-----------------------------------",
"settings": {}
},
{
"name": "Extra: Diff Range",
"scope": [
@@ -444,7 +404,6 @@
"meta.separator"
],
"settings": {
"background": "#DDDDFF",
"foreground": "#434343"
}
},
@@ -452,7 +411,6 @@
"name": "Extra: Diff From",
"scope": "meta.diff.header.from-file",
"settings": {
"background": "#FFDDDD",
"foreground": "#434343"
}
},
@@ -460,7 +418,6 @@
"name": "Extra: Diff To",
"scope": "meta.diff.header.to-file",
"settings": {
"background": "#DDFFDD",
"foreground": "#434343"
}
},
@@ -500,7 +457,6 @@
"editorLineNumber.activeForeground": "#9769dc",
"editor.selectionBackground": "#C9D0D9",
"minimap.selectionHighlight": "#C9D0D9",
"tab.modifiedBorder": "#f1897f",
"panel.background": "#F5F5F5",
"sideBar.background": "#F2F2F2",
"sideBarSectionHeader.background": "#ede8ef",

View File

@@ -5,7 +5,6 @@
"activityBar.background": "#580000",
"tab.inactiveBackground": "#300a0a",
"tab.activeBackground": "#490000",
"tab.modifiedBorder": "#db7e58",
"sideBar.background": "#330000",
"statusBar.background": "#700000",
"statusBar.noFolderBackground": "#700000",
@@ -62,12 +61,7 @@
"tokenColors": [
{
"settings": {
"background": "#390000",
"caret": "#970000",
"foreground": "#F8F8F8",
"invisibles": "#c10000",
"lineHighlight": "#0000004A",
"selection": "#750000"
}
},
{
@@ -76,7 +70,6 @@
"source.groovy.embedded"
],
"settings": {
"background": "#390000",
"foreground": "#F8F8F8"
}
},
@@ -148,24 +141,9 @@
"name": "Invalid",
"scope": "invalid",
"settings": {
"background": "#fd6209ff",
"foreground": "#ffffffff"
}
},
{
"name": "Embedded Source",
"scope": "text source",
"settings": {
"background": "#b0b3ba14"
}
},
{
"name": "Embedded Source (Bright)",
"scope": "text.html.ruby source",
"settings": {
"background": "#b1b3ba21"
}
},
{
"name": "Entity inherited-class",
"scope": "entity.other.inherited-class",
@@ -323,7 +301,6 @@
"meta.diff.header"
],
"settings": {
"background": "#0b2f20ff",
"fontStyle": "italic",
"foreground": "#f8f8f8ff"
}
@@ -332,7 +309,6 @@
"name": "diff.deleted",
"scope": "markup.deleted",
"settings": {
"background": "#fedcddff",
"foreground": "#ec9799ff"
}
},
@@ -340,7 +316,6 @@
"name": "diff.changed",
"scope": "markup.changed",
"settings": {
"background": "#c4b14aff",
"foreground": "#f8f8f8ff"
}
},
@@ -348,7 +323,6 @@
"name": "diff.inserted",
"scope": "markup.inserted",
"settings": {
"background": "#9bf199ff",
"foreground": "#41a83eff"
}
},

View File

@@ -3,14 +3,12 @@
"tokenColors": [
{
"settings": {
"background": "#002B36",
"foreground": "#93A1A1"
}
},
{
"scope": ["meta.embedded", "source.groovy.embedded"],
"settings": {
"background": "#002B36",
"foreground": "#93A1A1"
}
},
@@ -221,7 +219,6 @@
"meta.diff.header"
],
"settings": {
"background": "#b58900",
"fontStyle": "italic",
"foreground": "#E0EDDD"
}
@@ -230,7 +227,6 @@
"name": "diff: deleted",
"scope": "markup.deleted",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#dc322f"
}
@@ -239,7 +235,6 @@
"name": "diff: changed",
"scope": "markup.changed",
"settings": {
"background": "#eee8d5",
"fontStyle": "",
"foreground": "#cb4b16"
}
@@ -248,7 +243,6 @@
"name": "diff: inserted",
"scope": "markup.inserted",
"settings": {
"background": "#eee8d5",
"foreground": "#219186"
}
},
@@ -425,7 +419,6 @@
"tab.inactiveForeground": "#93A1A1",
"tab.inactiveBackground": "#004052",
"tab.border": "#003847",
"tab.modifiedBorder": "#268bd2",
// Workbench: Activity Bar
"activityBar.background": "#003847",

View File

@@ -3,14 +3,12 @@
"tokenColors": [
{
"settings": {
"background": "#FDF6E3",
"foreground": "#657B83"
}
},
{
"scope": ["meta.embedded", "source.groovy.embedded"],
"settings": {
"background": "#FDF6E3",
"foreground": "#657B83"
}
},