Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568

This commit is contained in:
ADS Merger
2020-04-30 00:53:43 +00:00
parent 87e5239713
commit 93f35ca321
413 changed files with 7190 additions and 8756 deletions

View File

@@ -21,6 +21,13 @@
"language": "dockerfile",
"scopeName": "source.dockerfile",
"path": "./syntaxes/docker.tmLanguage.json"
}]
}],
"configurationDefaults": {
"[dockerfile]": {
"editor.quickSuggestions": {
"strings": true
}
}
}
}
}
}

View File

@@ -609,10 +609,6 @@
"command": "git.pullRebase",
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
},
{
"command": "git.pullFrom",
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
},
{
"command": "git.merge",
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
@@ -1669,6 +1665,18 @@
"scope": "resource",
"default": true,
"description": "%config.showCommitInput%"
},
"git.terminalAuthentication": {
"type": "boolean",
"scope": "resource",
"default": true,
"description": "%config.terminalAuthentication%"
},
"git.githubAuthentication": {
"type": "boolean",
"scope": "resource",
"default": true,
"description": "%config.githubAuthentication%"
}
}
},
@@ -1781,6 +1789,7 @@
"ignore"
],
"filenames": [
".gitignore_global",
".gitignore"
],
"configuration": "./languages/ignore.language-configuration.json"

View File

@@ -144,6 +144,8 @@
"config.untrackedChanges.separate": "Untracked changes appear separately in the Source Control view. They are also excluded from several actions.",
"config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.",
"config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.",
"config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.",
"config.githubAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.",
"colors.added": "Color for added resources.",
"colors.modified": "Color for modified resources.",
"colors.deleted": "Color for deleted resources.",

View File

@@ -5,7 +5,7 @@
import { Model } from '../model';
import { Repository as BaseRepository, Resource } from '../repository';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType, RemoteSourceProvider } from './git';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType, RemoteSourceProvider, CredentialsProvider } from './git';
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode';
import { mapEvent } from '../util';
import { toGitUri } from '../uri';
@@ -183,6 +183,10 @@ export class ApiRepository implements Repository {
return this._repository.removeRemote(name);
}
renameRemote(name: string, newName: string): Promise<void> {
return this._repository.renameRemote(name, newName);
}
fetch(remote?: string | undefined, ref?: string | undefined, depth?: number | undefined): Promise<void> {
return this._repository.fetch(remote, ref, depth);
}
@@ -248,10 +252,21 @@ export class ApiImpl implements API {
return result ? new ApiRepository(result) : null;
}
async init(root: Uri): Promise<Repository | null> {
const path = root.fsPath;
await this._model.git.init(path);
await this._model.openRepository(path);
return this.getRepository(root) || null;
}
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
return this._model.registerRemoteSourceProvider(provider);
}
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
return this._model.registerCredentialsProvider(provider);
}
constructor(private _model: Model) { }
}

View File

@@ -179,6 +179,7 @@ export interface Repository {
addRemote(name: string, url: string): Promise<void>;
removeRemote(name: string): Promise<void>;
renameRemote(name: string, newName: string): Promise<void>;
fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
pull(unshallow?: boolean): Promise<void>;
@@ -203,6 +204,15 @@ export interface RemoteSourceProvider {
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}
export interface Credentials {
readonly username: string;
readonly password: string;
}
export interface CredentialsProvider {
getCredentials(host: Uri): ProviderResult<Credentials>;
}
export type APIState = 'uninitialized' | 'initialized';
export interface API {
@@ -215,7 +225,10 @@ export interface API {
toGitUri(uri: Uri, ref: string): Uri;
getRepository(uri: Uri): Repository | null;
init(root: Uri): Promise<Repository | null>;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
}
export interface GitExtension {

View File

@@ -1,5 +1,5 @@
#!/bin/sh
VSCODE_GIT_ASKPASS_PIPE=`mktemp`
VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $*
ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $*
cat $VSCODE_GIT_ASKPASS_PIPE
rm $VSCODE_GIT_ASKPASS_PIPE
rm $VSCODE_GIT_ASKPASS_PIPE

View File

@@ -3,36 +3,60 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, InputBoxOptions } from 'vscode';
import { IDisposable } from './util';
import { window, InputBoxOptions, Uri, OutputChannel, Disposable } from 'vscode';
import { IDisposable, EmptyDisposable, toDisposable } from './util';
import * as path from 'path';
import { IIPCHandler, IIPCServer } from './ipc/ipcServer';
export interface AskpassEnvironment {
GIT_ASKPASS: string;
ELECTRON_RUN_AS_NODE?: string;
VSCODE_GIT_ASKPASS_NODE?: string;
VSCODE_GIT_ASKPASS_MAIN?: string;
VSCODE_GIT_ASKPASS_HANDLE?: string;
}
import { IIPCHandler, IIPCServer, createIPCServer } from './ipc/ipcServer';
import { CredentialsProvider, Credentials } from './api/git';
export class Askpass implements IIPCHandler {
private disposable: IDisposable;
private disposable: IDisposable = EmptyDisposable;
private cache = new Map<string, Credentials>();
private credentialsProviders = new Set<CredentialsProvider>();
static getDisabledEnv(): AskpassEnvironment {
return {
GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh')
};
static async create(outputChannel: OutputChannel, context?: string): Promise<Askpass> {
try {
return new Askpass(await createIPCServer(context));
} catch (err) {
outputChannel.appendLine(`[error] Failed to create git askpass IPC: ${err}`);
return new Askpass();
}
}
constructor(ipc: IIPCServer) {
this.disposable = ipc.registerHandler('askpass', this);
private constructor(private ipc?: IIPCServer) {
if (ipc) {
this.disposable = ipc.registerHandler('askpass', this);
}
}
async handle({ request, host }: { request: string, host: string }): Promise<string> {
const uri = Uri.parse(host);
const authority = uri.authority.replace(/^.*@/, '');
const password = /password/i.test(request);
const cached = this.cache.get(authority);
if (cached && password) {
this.cache.delete(authority);
return cached.password;
}
if (!password) {
for (const credentialsProvider of this.credentialsProviders) {
try {
const credentials = await credentialsProvider.getCredentials(uri);
if (credentials) {
this.cache.set(authority, credentials);
setTimeout(() => this.cache.delete(authority), 60_000);
return credentials.username;
}
} catch { }
}
}
const options: InputBoxOptions = {
password: /password/i.test(request),
password,
placeHolder: request,
prompt: `Git: ${host}`,
ignoreFocusOut: true
@@ -41,15 +65,26 @@ export class Askpass implements IIPCHandler {
return await window.showInputBox(options) || '';
}
getEnv(): AskpassEnvironment {
getEnv(): { [key: string]: string; } {
if (!this.ipc) {
return {
GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh')
};
}
return {
ELECTRON_RUN_AS_NODE: '1',
...this.ipc.getEnv(),
GIT_ASKPASS: path.join(__dirname, 'askpass.sh'),
VSCODE_GIT_ASKPASS_NODE: process.execPath,
VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js')
};
}
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
this.credentialsProviders.add(provider);
return toDisposable(() => this.credentialsProviders.delete(provider));
}
dispose(): void {
this.disposable.dispose();
}

View File

@@ -524,7 +524,7 @@ export class CommandCenter {
quickpick.ignoreFocusOut = true;
const providers = this.model.getRemoteProviders()
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('clonefrom', "Clone from {1}", provider.name), alwaysShow: true, provider }));
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('clonefrom', "Clone from {0}", provider.name), alwaysShow: true, provider }));
quickpick.placeholder = providers.length === 0
? localize('provide url', "Provide repository URL.")
@@ -2560,6 +2560,14 @@ export class CommandCenter {
type = 'warning';
options.modal = false;
break;
case GitErrorCodes.AuthenticationFailed:
const regex = /Authentication failed for '(.*)'/i;
const match = regex.exec(err.stderr || String(err));
message = match
? localize('auth failed specific', "Failed to authenticate to git remote:\n\n{0}", match[1])
: localize('auth failed', "Failed to authenticate to git remote.");
break;
case GitErrorCodes.NoUserNameConfigured:
case GitErrorCodes.NoUserEmailConfigured:
message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git.");

View File

@@ -306,7 +306,7 @@ export interface IGitOptions {
function getGitErrorCode(stderr: string): string | undefined {
if (/Another git process seems to be running in this repository|If no other git process is currently running/.test(stderr)) {
return GitErrorCodes.RepositoryIsLocked;
} else if (/Authentication failed/.test(stderr)) {
} else if (/Authentication failed/i.test(stderr)) {
return GitErrorCodes.AuthenticationFailed;
} else if (/Not a git repository/i.test(stderr)) {
return GitErrorCodes.NotAGitRepository;
@@ -1498,7 +1498,12 @@ export class Repository {
}
async removeRemote(name: string): Promise<void> {
const args = ['remote', 'rm', name];
const args = ['remote', 'remove', name];
await this.run(args);
}
async renameRemote(name: string, newName: string): Promise<void> {
const args = ['remote', 'rename', name, newName];
await this.run(args);
}

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CredentialsProvider, Credentials } from './api/git';
import { IDisposable, filterEvent, EmptyDisposable } from './util';
import { workspace, Uri, AuthenticationSession, authentication } from 'vscode';
import { Askpass } from './askpass';
export class GitHubCredentialProvider implements CredentialsProvider {
async getCredentials(host: Uri): Promise<Credentials | undefined> {
if (!/github\.com/i.test(host.authority)) {
return;
}
const session = await this.getSession();
return { username: session.account.id, password: await session.getAccessToken() };
}
private async getSession(): Promise<AuthenticationSession> {
const authenticationSessions = await authentication.getSessions('github', ['repo']);
if (authenticationSessions.length) {
return await authenticationSessions[0];
} else {
return await authentication.login('github', ['repo']);
}
}
}
export class GithubCredentialProviderManager {
private providerDisposable: IDisposable = EmptyDisposable;
private readonly disposable: IDisposable;
private _enabled = false;
private set enabled(enabled: boolean) {
if (this._enabled === enabled) {
return;
}
this._enabled = enabled;
if (enabled) {
this.providerDisposable = this.askpass.registerCredentialsProvider(new GitHubCredentialProvider());
} else {
this.providerDisposable.dispose();
}
}
constructor(private readonly askpass: Askpass) {
this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'))(this.refresh, this);
this.refresh();
}
private refresh(): void {
this.enabled = workspace.getConfiguration('git', null).get('githubAuthentication', true);
}
dispose(): void {
this.enabled = false;
this.disposable.dispose();
}
}

View File

@@ -11,27 +11,42 @@ import * as os from 'os';
import * as fs from 'fs';
import * as crypto from 'crypto';
function getIPCHandlePath(nonce: string): string {
function getIPCHandlePath(id: string): string {
if (process.platform === 'win32') {
return `\\\\.\\pipe\\vscode-git-ipc-${nonce}-sock`;
return `\\\\.\\pipe\\vscode-git-${id}-sock`;
}
if (process.env['XDG_RUNTIME_DIR']) {
return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-ipc-${nonce}.sock`);
return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-${id}.sock`);
}
return path.join(os.tmpdir(), `vscode-git-ipc-${nonce}.sock`);
return path.join(os.tmpdir(), `vscode-git-${id}.sock`);
}
export interface IIPCHandler {
handle(request: any): Promise<any>;
}
export async function createIPCServer(): Promise<IIPCServer> {
export async function createIPCServer(context?: string): Promise<IIPCServer> {
const server = http.createServer();
const buffer = await new Promise<Buffer>((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf)));
const nonce = buffer.toString('hex');
const ipcHandlePath = getIPCHandlePath(nonce);
const hash = crypto.createHash('sha1');
if (!context) {
const buffer = await new Promise<Buffer>((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf)));
hash.update(buffer);
} else {
hash.update(context);
}
const ipcHandlePath = getIPCHandlePath(hash.digest('hex').substr(0, 10));
if (process.platform !== 'win32') {
try {
await fs.promises.unlink(ipcHandlePath);
} catch {
// noop
}
}
return new Promise((c, e) => {
try {
@@ -46,7 +61,7 @@ export async function createIPCServer(): Promise<IIPCServer> {
export interface IIPCServer extends Disposable {
readonly ipcHandlePath: string | undefined;
getEnv(): any;
getEnv(): { [key: string]: string; };
registerHandler(name: string, handler: IIPCHandler): Disposable;
}
@@ -91,7 +106,7 @@ class IPCServer implements IIPCServer, Disposable {
});
}
getEnv(): any {
getEnv(): { [key: string]: string; } {
return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath };
}

View File

@@ -20,9 +20,10 @@ import { GitProtocolHandler } from './protocolHandler';
import { GitExtensionImpl } from './api/extension';
// import * as path from 'path';
// import * as fs from 'fs';
import { createIPCServer, IIPCServer } from './ipc/ipcServer';
import { GitTimelineProvider } from './timelineProvider';
import { registerAPICommands } from './api/api1';
import { GithubCredentialProviderManager } from './github';
import { TerminalEnvironmentManager } from './terminal';
const deactivateTasks: { (): Promise<any>; }[] = [];
@@ -36,27 +37,18 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
const pathHint = workspace.getConfiguration('git').get<string>('path');
const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path)));
let env: any = {};
let ipc: IIPCServer | undefined;
const askpass = await Askpass.create(outputChannel, context.storagePath);
disposables.push(askpass);
try {
ipc = await createIPCServer();
disposables.push(ipc);
env = { ...env, ...ipc.getEnv() };
} catch {
// noop
}
const env = askpass.getEnv();
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, env);
disposables.push(terminalEnvironmentManager);
if (ipc) {
const askpass = new Askpass(ipc);
disposables.push(askpass);
env = { ...env, ...askpass.getEnv() };
} else {
env = { ...env, ...Askpass.getDisabledEnv() };
}
const githubCredentialProviderManager = new GithubCredentialProviderManager(askpass);
context.subscriptions.push(githubCredentialProviderManager);
const git = new Git({ gitPath: info.path, version: info.version, env });
const model = new Model(git, context.globalState, outputChannel);
const model = new Model(git, askpass, context.globalState, outputChannel);
disposables.push(model);
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);

View File

@@ -12,7 +12,8 @@ import * as path from 'path';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
import { fromGitUri } from './uri';
import { GitErrorCodes, APIState as State, RemoteSourceProvider } from './api/git';
import { GitErrorCodes, APIState as State, RemoteSourceProvider, CredentialsProvider } from './api/git';
import { Askpass } from './askpass';
const localize = nls.loadMessageBundle();
@@ -78,7 +79,7 @@ export class Model {
private disposables: Disposable[] = [];
constructor(readonly git: Git, private globalState: Memento, private outputChannel: OutputChannel) {
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannel: OutputChannel) {
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
@@ -454,6 +455,10 @@ export class Model {
return toDisposable(() => this.remoteProviders.delete(provider));
}
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
return this.askpass.registerCredentialsProvider(provider);
}
getRemoteProviders(): RemoteSourceProvider[] {
return [...this.remoteProviders.values()];
}

View File

@@ -1096,6 +1096,10 @@ export class Repository implements Disposable {
await this.run(Operation.Remote, () => this.repository.removeRemote(name));
}
async renameRemote(name: string, newName: string): Promise<void> {
await this.run(Operation.Remote, () => this.repository.renameRemote(name, newName));
}
@throttle
async fetchDefault(options: { silent?: boolean } = {}): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch(options));

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext, workspace } from 'vscode';
import { filterEvent, IDisposable } from './util';
export class TerminalEnvironmentManager {
private readonly disposable: IDisposable;
private _enabled = false;
private set enabled(enabled: boolean) {
if (this._enabled === enabled) {
return;
}
this._enabled = enabled;
this.context.environmentVariableCollection.clear();
if (enabled) {
for (const name of Object.keys(this.env)) {
this.context.environmentVariableCollection.replace(name, this.env[name]);
}
}
}
constructor(private readonly context: ExtensionContext, private readonly env: { [key: string]: string }) {
this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'))
(this.refresh, this);
this.refresh();
}
private refresh(): void {
this.enabled = workspace.getConfiguration('git', null).get('terminalAuthentication', true);
}
dispose(): void {
this.disposable.dispose();
}
}

View File

@@ -14,6 +14,22 @@
"activationEvents": [
"*"
],
"contributes": {
"commands": [
{
"command": "github.provide-token",
"title": "Manually Provide Token"
}
],
"menus": {
"commandPalette": [
{
"command": "github.provide-token",
"when": "false"
}
]
}
},
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"main": "./out/extension.js",
"scripts": {

View File

@@ -18,6 +18,10 @@ export async function activate(context: vscode.ExtensionContext) {
await loginService.initialize();
context.subscriptions.push(vscode.commands.registerCommand('github.provide-token', () => {
return loginService.manuallyProvideToken();
}));
vscode.authentication.registerAuthenticationProvider({
id: 'github',
displayName: 'GitHub',

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import * as uuid from 'uuid';
import { keychain } from './common/keychain';
import { GitHubServer } from './githubServer';
import { GitHubServer, NETWORK_ERROR } from './githubServer';
import Logger from './common/logger';
export const onDidChangeSessions = new vscode.EventEmitter<vscode.AuthenticationSessionsChangeEvent>();
@@ -38,13 +38,26 @@ export class GitHubAuthenticationProvider {
private _githubServer = new GitHubServer();
public async initialize(): Promise<void> {
this._sessions = await this.readSessions();
try {
this._sessions = await this.readSessions();
} catch (e) {
// Ignore, network request failed
}
// TODO revert Cannot validate tokens from auth server, no available clientId
// await this.validateSessions();
this.pollForChange();
}
private pollForChange() {
setTimeout(async () => {
const storedSessions = await this.readSessions();
let storedSessions: vscode.AuthenticationSession[];
try {
storedSessions = await this.readSessions();
} catch (e) {
// Ignore, network request failed
return;
}
const added: string[] = [];
const removed: string[] = [];
@@ -53,6 +66,7 @@ export class GitHubAuthenticationProvider {
const matchesExisting = this._sessions.some(s => s.id === session.id);
// Another window added a session to the keychain, add it to our state as well
if (!matchesExisting) {
Logger.info('Adding session found in keychain');
this._sessions.push(session);
added.push(session.id);
}
@@ -62,6 +76,7 @@ export class GitHubAuthenticationProvider {
const matchesExisting = storedSessions.some(s => s.id === session.id);
// Another window has logged out, remove from our state
if (!matchesExisting) {
Logger.info('Removing session no longer found in keychain');
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
if (sessionIndex > -1) {
this._sessions.splice(sessionIndex, 1);
@@ -84,35 +99,36 @@ export class GitHubAuthenticationProvider {
if (storedSessions) {
try {
const sessionData: (SessionData | OldSessionData)[] = JSON.parse(storedSessions);
const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise<vscode.AuthenticationSession | undefined> => {
try {
const needsUserInfo = isOldSessionData(session) || !session.account;
let userInfo: { id: string, accountName: string };
if (needsUserInfo) {
userInfo = await this._githubServer.getUserInfo(session.accessToken);
}
return {
id: session.id,
account: {
displayName: isOldSessionData(session)
? session.accountName
: session.account?.displayName ?? userInfo!.accountName,
id: isOldSessionData(session)
? userInfo!.id
: session.account?.id ?? userInfo!.id
},
scopes: session.scopes,
getAccessToken: () => Promise.resolve(session.accessToken)
};
} catch (e) {
return undefined;
const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise<vscode.AuthenticationSession> => {
const needsUserInfo = isOldSessionData(session) || !session.account;
let userInfo: { id: string, accountName: string };
if (needsUserInfo) {
userInfo = await this._githubServer.getUserInfo(session.accessToken);
}
return {
id: session.id,
account: {
displayName: isOldSessionData(session)
? session.accountName
: session.account?.displayName ?? userInfo!.accountName,
id: isOldSessionData(session)
? userInfo!.id
: session.account?.id ?? userInfo!.id
},
scopes: session.scopes,
getAccessToken: () => Promise.resolve(session.accessToken)
};
});
return (await Promise.all(sessionPromises)).filter((x: vscode.AuthenticationSession | undefined): x is vscode.AuthenticationSession => !!x);
return Promise.all(sessionPromises);
} catch (e) {
if (e === NETWORK_ERROR) {
return [];
}
Logger.error(`Error reading sessions: ${e}`);
await keychain.deleteToken();
}
}
@@ -154,6 +170,10 @@ export class GitHubAuthenticationProvider {
}
}
public async manuallyProvideToken(): Promise<void> {
this._githubServer.manuallyProvideToken();
}
private async tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
const userInfo = await this._githubServer.getUserInfo(token);
return {
@@ -180,13 +200,15 @@ export class GitHubAuthenticationProvider {
public async logout(id: string) {
const sessionIndex = this._sessions.findIndex(session => session.id === id);
if (sessionIndex > -1) {
const session = this._sessions.splice(sessionIndex, 1)[0];
const token = await session.getAccessToken();
try {
await this._githubServer.revokeToken(token);
} catch (_) {
// ignore, should still remove from keychain
}
this._sessions.splice(sessionIndex, 1);
// TODO revert
// Cannot revoke tokens from auth server, no clientId available
// const token = await session.getAccessToken();
// try {
// await this._githubServer.revokeToken(token);
// } catch (_) {
// // ignore, should still remove from keychain
// }
}
await this.storeSessions();

View File

@@ -4,11 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import * as https from 'https';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as uuid from 'uuid';
import { PromiseAdapter, promiseFromEvent } from './common/utils';
import Logger from './common/logger';
import ClientRegistrar, { ClientDetails } from './common/clientRegistrar';
import ClientRegistrar from './common/clientRegistrar';
const localize = nls.loadMessageBundle();
export const NETWORK_ERROR = 'network error';
const AUTH_RELAY_SERVER = 'vscode-auth.github.com';
class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.UriHandler {
public handleUri(uri: vscode.Uri) {
@@ -18,8 +24,8 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
export const uriHandler = new UriEventHandler;
const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter<vscode.Uri, string> =
(state, clientDetails) => async (uri, resolve, reject) => {
const exchangeCodeForToken: (state: string, host: string, getPath: (code: string) => string) => PromiseAdapter<vscode.Uri, string> =
(state, host, getPath) => async (uri, resolve, reject) => {
Logger.info('Exchanging code for token...');
const query = parseQuery(uri);
const code = query.code;
@@ -30,8 +36,8 @@ const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => Pro
}
const post = https.request({
host: 'github.com',
path: `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${query.state}&code=${code}`,
host: host,
path: getPath(code),
method: 'POST',
headers: {
Accept: 'application/json'
@@ -67,15 +73,61 @@ function parseQuery(uri: vscode.Uri) {
}
export class GitHubServer {
private _statusBarItem: vscode.StatusBarItem | undefined;
public async login(scopes: string): Promise<string> {
Logger.info('Logging in...');
this.updateStatusBarItem(true);
const state = uuid();
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
const clientDetails = scopes === 'vso' ? ClientRegistrar.getGitHubAppDetails() : ClientRegistrar.getClientDetails(callbackUri);
const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`);
let uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code`);
if (scopes === 'vso') {
const clientDetails = ClientRegistrar.getGitHubAppDetails();
uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`);
}
vscode.env.openExternal(uri);
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state,
scopes === 'vso' ? 'github.com' : AUTH_RELAY_SERVER,
(code) => {
if (scopes === 'vso') {
const clientDetails = ClientRegistrar.getGitHubAppDetails();
return `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${state}&code=${code}`;
} else {
return `/token?code=${code}&state=${state}`;
}
})).finally(() => {
this.updateStatusBarItem(false);
});
}
private updateStatusBarItem(isStart?: boolean) {
if (isStart && !this._statusBarItem) {
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
this._statusBarItem.text = localize('signingIn', "$(mark-github) Signing in to github.com...");
this._statusBarItem.command = 'github.provide-token';
this._statusBarItem.show();
}
if (!isStart && this._statusBarItem) {
this._statusBarItem.dispose();
this._statusBarItem = undefined;
}
}
public async manuallyProvideToken() {
const uriOrToken = await vscode.window.showInputBox({ prompt: 'Token', ignoreFocusOut: true });
if (!uriOrToken) { return; }
try {
const uri = vscode.Uri.parse(uriOrToken);
if (!uri.scheme || uri.scheme === 'file') { throw new Error; }
uriHandler.handleUri(uri);
} catch (e) {
Logger.error(e);
vscode.window.showErrorMessage(localize('unexpectedInput', "The input did not matched the expected format"));
}
}
public async hasUserInstallation(token: string): Promise<boolean> {
@@ -120,7 +172,7 @@ export class GitHubServer {
const uri = vscode.Uri.parse(`https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${state}`);
vscode.env.openExternal(uri);
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails));
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, 'github.com', (code) => `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${state}&code=${code}`));
}
public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> {
@@ -145,7 +197,7 @@ export class GitHubServer {
Logger.info('Got account info!');
resolve({ id: json.id, accountName: json.login });
} else {
Logger.error('Getting account info failed');
Logger.error(`Getting account info failed: ${result.statusMessage}`);
reject(new Error(result.statusMessage));
}
});
@@ -153,7 +205,52 @@ export class GitHubServer {
post.end();
post.on('error', err => {
reject(err);
Logger.error(err.message);
reject(new Error(NETWORK_ERROR));
});
});
}
public async validateToken(token: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
const clientDetails = ClientRegistrar.getClientDetails(callbackUri);
const detailsString = `${clientDetails.id}:${clientDetails.secret}`;
const payload = JSON.stringify({ access_token: token });
Logger.info('Validating token...');
const post = https.request({
host: 'api.github.com',
path: `/applications/${clientDetails.id}/token`,
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(detailsString).toString('base64')}`,
'User-Agent': 'Visual-Studio-Code',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
}
}, result => {
const buffer: Buffer[] = [];
result.on('data', (chunk: Buffer) => {
buffer.push(chunk);
});
result.on('end', () => {
if (result.statusCode === 200) {
Logger.info('Validated token!');
resolve();
} else {
Logger.info(`Validating token failed: ${result.statusMessage}`);
reject(new Error(result.statusMessage));
}
});
});
post.write(payload);
post.end();
post.on('error', err => {
Logger.error(err.message);
reject(new Error(NETWORK_ERROR));
});
});
}

View File

@@ -24,7 +24,7 @@ export function activate(context: vscode.ExtensionContext) {
const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
context.subscriptions.push(vscode.window.registerCustomEditorProvider2(PreviewManager.viewType, previewManager, {
supportsMultipleEditorsPerResource: true,
supportsMultipleEditorsPerDocument: true,
}));
context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => {

View File

@@ -13,7 +13,7 @@ import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
const localize = nls.loadMessageBundle();
export class PreviewManager implements vscode.CustomEditorProvider {
export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
public static readonly viewType = 'imagePreview.previewEditor';

View File

@@ -120,10 +120,10 @@
]
},
"dependencies": {
"request-light": "^0.2.5",
"request-light": "^0.3.0",
"vscode-extension-telemetry": "0.1.1",
"vscode-languageclient": "^6.1.1",
"vscode-nls": "^4.1.1"
"vscode-languageclient": "^6.1.3",
"vscode-nls": "^4.1.2"
},
"devDependencies": {
"@types/node": "^12.11.7"

View File

@@ -23,6 +23,7 @@ The server implements the following capabilities of the language server protocol
- [Code Formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting) supporting ranges and formatting the whole document.
- [Folding Ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) for all folding ranges in the document.
- Semantic Selection for semantic selection for one or multiple cursor positions.
- [Goto Definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) for $ref references in JSON schemas
- [Diagnostics (Validation)](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics) are pushed for all open documents
- syntax errors
- structural validation based on the document's [JSON schema](http://json-schema.org/).

View File

@@ -13,8 +13,8 @@
"main": "./out/jsonServerMain",
"dependencies": {
"jsonc-parser": "^2.2.1",
"request-light": "^0.2.5",
"vscode-json-languageservice": "^3.5.2",
"request-light": "^0.3.0",
"vscode-json-languageservice": "^3.6.0",
"vscode-languageserver": "^6.1.1",
"vscode-uri": "^2.1.1"
},

View File

@@ -174,7 +174,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
colorProvider: {},
foldingRangeProvider: true,
selectionRangeProvider: true
selectionRangeProvider: true,
definitionProvider: true
};
return { capabilities };
@@ -516,5 +517,16 @@ connection.onSelectionRanges((params, token) => {
}, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
});
connection.onDefinition((params, token) => {
return runSafeAsync(async () => {
const document = documents.get(params.textDocument.uri);
if (document) {
const jsonDocument = getJSONDocument(document);
return languageService.findDefinition(document, params.position, jsonDocument);
}
return [];
}, [], `Error while computing definitions for ${params.textDocument.uri}`, token);
});
// Listen on the connection
connection.listen();

View File

@@ -53,7 +53,7 @@ http-proxy-agent@^2.1.0:
agent-base "4"
debug "3.1.0"
https-proxy-agent@^2.2.3:
https-proxy-agent@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
@@ -71,24 +71,24 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
request-light@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746"
integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw==
request-light@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d"
integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA==
dependencies:
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.3"
https-proxy-agent "^2.2.4"
vscode-nls "^4.1.1"
vscode-json-languageservice@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.2.tgz#4b898140a8e581359c10660845a4cae15dcbb4f9"
integrity sha512-9cUvBq00O08lpWVVOx6tQ1yLxCHss79nsUdEAVYGomRyMbnPBmc0AkYPcXI9WK1EM6HBo0R9Zo3NjFhcICpy4A==
vscode-json-languageservice@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.6.0.tgz#133a1e2c3a3dffe38564a1ba948516805c3c1869"
integrity sha512-dXzFywypUZ9T0tjr4fREZiknXDz6vAGx1zsxbQY1+9DOpjMfbz0VLP873KmcbuvL4K3nseKTxc4TKHu8kLXRMw==
dependencies:
jsonc-parser "^2.2.1"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.15.1"
vscode-nls "^4.1.1"
vscode-nls "^4.1.2"
vscode-uri "^2.1.1"
vscode-jsonrpc@^5.0.1:
@@ -126,6 +126,11 @@ vscode-nls@^4.1.1:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
vscode-nls@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
vscode-uri@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90"

View File

@@ -76,7 +76,7 @@ http-proxy-agent@^2.1.0:
agent-base "4"
debug "3.1.0"
https-proxy-agent@^2.2.3:
https-proxy-agent@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
@@ -94,13 +94,13 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
request-light@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746"
integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw==
request-light@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d"
integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA==
dependencies:
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.3"
https-proxy-agent "^2.2.4"
vscode-nls "^4.1.1"
semver@^5.3.0:
@@ -125,10 +125,10 @@ vscode-jsonrpc@^5.0.1:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794"
integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==
vscode-languageclient@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457"
integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA==
vscode-languageclient@^6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a"
integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==
dependencies:
semver "^6.3.0"
vscode-languageserver-protocol "^3.15.3"
@@ -151,6 +151,11 @@ vscode-nls@^4.1.1:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
vscode-nls@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"

View File

@@ -313,7 +313,7 @@
"customEditors": [
{
"viewType": "vscode.markdown.preview.editor",
"displayName": "(Experimental) VS Code Markdown Preview",
"displayName": "Markdown Preview (Experimental)",
"priority": "option",
"selector": [
{

View File

@@ -104,7 +104,7 @@
"variable.other.enummember"
],
"settings": {
"foreground": "#D4D4D4",
"foreground": "#51B6C4",
}
},
{

View File

@@ -104,7 +104,7 @@
"variable.other.enummember"
],
"settings": {
"foreground": "#000000",
"foreground": "#328267",
}
},
{

View File

@@ -17,18 +17,6 @@
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "microsoft.signin",
"title": "%signIn%",
"category": "%displayName%"
},
{
"command": "microsoft.signout",
"title": "%signOut%",
"category": "%displayName%"
}
],
"configuration": {
"title": "Microsoft Account",
"properties": {

View File

@@ -5,11 +5,8 @@
import * as vscode from 'vscode';
import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper';
import * as nls from 'vscode-nls';
import TelemetryReporter from 'vscode-extension-telemetry';
const localize = nls.loadMessageBundle();
export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access';
export async function activate(context: vscode.ExtensionContext) {
@@ -48,39 +45,6 @@ export async function activate(context: vscode.ExtensionContext) {
}
}));
context.subscriptions.push(vscode.commands.registerCommand('microsoft.signin', () => {
return loginService.login(DEFAULT_SCOPES);
}));
context.subscriptions.push(vscode.commands.registerCommand('microsoft.signout', async () => {
const sessions = loginService.sessions;
if (sessions.length === 0) {
return;
}
if (sessions.length === 1) {
const id = loginService.sessions[0].id;
await loginService.logout(id);
onDidChangeSessions.fire({ added: [], removed: [id], changed: [] });
vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out."));
return;
}
const selectedSession = await vscode.window.showQuickPick(sessions.map(session => {
return {
id: session.id,
label: session.account.displayName
};
}));
if (selectedSession) {
await loginService.logout(selectedSession.id);
onDidChangeSessions.fire({ added: [], removed: [selectedSession.id], changed: [] });
vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out."));
return;
}
}));
return;
}

View File

@@ -1,2 +0,0 @@
out
node_modules

View File

@@ -1,17 +0,0 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.1.0",
"configurations": [
{
"name": "Launch Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["${workspaceFolder}/../../", "${workspaceFolder}/test", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceFolder}/out",
"preLaunchTask": "npm"
}
]
}

View File

@@ -1,11 +0,0 @@
{
"version": "2.0.0",
"command": "npm",
"type": "shell",
"presentation": {
"reveal": "silent"
},
"args": ["run", "compile"],
"isBackground": true,
"problemMatcher": "$tsc-watch"
}

View File

@@ -1,59 +0,0 @@
{
"name": "vscode-colorize-tests",
"description": "Colorize tests for VS Code",
"version": "0.0.1",
"publisher": "vscode",
"license": "MIT",
"private": true,
"activationEvents": [
"onLanguage:json"
],
"main": "./out/colorizerTestMain",
"enableProposedApi": true,
"engines": {
"vscode": "*"
},
"scripts": {
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json"
},
"dependencies": {
"jsonc-parser": "2.2.1"
},
"devDependencies": {
"@types/node": "^12.11.7",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"vscode": "1.1.5"
},
"contributes": {
"semanticTokenTypes": [
{
"id": "testToken",
"description": "A test token"
}
],
"semanticTokenModifiers": [
{
"id": "testModifier",
"description": "A test modifier"
}
],
"semanticTokenScopes": [
{
"scopes": {
"testToken": [
"entity.name.function.special"
]
}
}
],
"productIconThemes": [
{
"id": "Test Product Icons",
"label": "The Test Product Icon Theme",
"path": "./producticons/test-product-icon-theme.json",
"_watch": true
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) <2013> <Elegant Themes, Inc.>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,43 +0,0 @@
{
// ElegantIcons from https://www.elegantthemes.com/icons/elegant_font.zip
"fonts": [
{
"id": "elegant",
"src": [
{
"path": "./ElegantIcons.woff",
"format": "woff"
}
],
"weight": "normal",
"style": "normal",
}
],
"iconDefinitions": {
"chevron-down": {
"fontCharacter": "\\43",
},
"chevron-right": {
"fontCharacter": "\\45"
},
"error": {
"fontCharacter": "\\e062"
},
"warning": {
"fontCharacter": "\\e063"
},
"settings-gear": {
"fontCharacter": "\\e030"
},
"files": {
"fontCharacter": "\\e056"
},
"extensions": {
"fontCharacter": "\\e015"
},
"debug-alt-2": {
"fontCharacter": "\\e072"
}
}
}

View File

@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import { commands, Uri } from 'vscode';
import { join, basename, normalize, dirname } from 'path';
import * as fs from 'fs';
function assertUnchangedTokens(testFixurePath: string, done: any) {
let fileName = basename(testFixurePath);
return commands.executeCommand('_workbench.captureSyntaxTokens', Uri.file(testFixurePath)).then(data => {
try {
let resultsFolderPath = join(dirname(dirname(testFixurePath)), 'colorize-results');
if (!fs.existsSync(resultsFolderPath)) {
fs.mkdirSync(resultsFolderPath);
}
let resultPath = join(resultsFolderPath, fileName.replace('.', '_') + '.json');
if (fs.existsSync(resultPath)) {
let previousData = JSON.parse(fs.readFileSync(resultPath).toString());
try {
assert.deepEqual(data, previousData);
} catch (e) {
fs.writeFileSync(resultPath, JSON.stringify(data, null, '\t'), { flag: 'w' });
if (Array.isArray(data) && Array.isArray(previousData) && data.length === previousData.length) {
for (let i= 0; i < data.length; i++) {
let d = data[i];
let p = previousData[i];
if (d.c !== p.c || hasThemeChange(d.r, p.r)) {
throw e;
}
}
// different but no tokenization ot color change: no failure
} else {
throw e;
}
}
} else {
fs.writeFileSync(resultPath, JSON.stringify(data, null, '\t'));
}
done();
} catch (e) {
done(e);
}
}, done);
}
function hasThemeChange(d: any, p: any) : boolean {
let keys = Object.keys(d);
for (let key of keys) {
if (d[key] !== p[key]) {
return true;
}
}
return false;
}
suite('colorization', () => {
let extensionsFolder = normalize(join(__dirname, '../../'));
let extensions = fs.readdirSync(extensionsFolder);
extensions.forEach(extension => {
let extensionColorizeFixturePath = join(extensionsFolder, extension, 'test', 'colorize-fixtures');
if (fs.existsSync(extensionColorizeFixturePath)) {
let fixturesFiles = fs.readdirSync(extensionColorizeFixturePath);
fixturesFiles.forEach(fixturesFile => {
// define a test for each fixture
test(extension + '-' + fixturesFile, function (done) {
assertUnchangedTokens(join(extensionColorizeFixturePath, fixturesFile), done);
});
});
}
});
});

View File

@@ -1,66 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as jsoncParser from 'jsonc-parser';
export function activate(context: vscode.ExtensionContext): any {
const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable', 'testToken'];
const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async', 'testModifier'];
const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
const outputChannel = vscode.window.createOutputChannel('Semantic Tokens Test');
const documentSemanticHighlightProvider: vscode.DocumentSemanticTokensProvider = {
provideDocumentSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult<vscode.SemanticTokens> {
const builder = new vscode.SemanticTokensBuilder();
function addToken(value: string, startLine: number, startCharacter: number, length: number) {
const [type, ...modifiers] = value.split('.');
let tokenType = legend.tokenTypes.indexOf(type);
if (tokenType === -1) {
return;
}
let tokenModifiers = 0;
for (let i = 0; i < modifiers.length; i++) {
const index = legend.tokenModifiers.indexOf(modifiers[i]);
if (index !== -1) {
tokenModifiers = tokenModifiers | 1 << index;
}
}
builder.push(startLine, startCharacter, length, tokenType, tokenModifiers);
const selectedModifiers = legend.tokenModifiers.filter((_val, bit) => tokenModifiers & (1 << bit)).join(' ');
outputChannel.appendLine(`line: ${startLine}, character: ${startCharacter}, length ${length}, ${legend.tokenTypes[tokenType]} (${tokenType}), ${selectedModifiers} ${tokenModifiers.toString(2)}`);
}
outputChannel.appendLine('---');
const visitor: jsoncParser.JSONVisitor = {
onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => {
addToken(property, startLine, startCharacter, property.length + 2);
},
onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => {
if (typeof value === 'string') {
addToken(value, startLine, startCharacter, length);
}
}
};
jsoncParser.visit(document.getText(), visitor);
return builder.build();
}
};
context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, documentSemanticHighlightProvider, legend));
}

View File

@@ -1,30 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const testRunner = require('vscode/lib/testrunner');
const suite = 'Integration Colorize Tests';
const options: any = {
ui: 'tdd',
useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
timeout: 60000
};
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
options.reporter = 'mocha-multi-reporters';
options.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(options);
export = testRunner;

View File

@@ -1,9 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../../../../src/vs/vscode.d.ts" />
/// <reference path="../../../../src/vs/vscode.proposed.d.ts" />
/// <reference types='@types/node'/>

View File

@@ -1,13 +0,0 @@
{
"editor.tokenColorCustomizationsExperimental": {
"class": "#00b0b0",
"interface": "#845faf",
"function": "#ff00ff",
"*.declaration": {
"fontStyle": "underline"
},
"*.declaration.member": {
"fontStyle": "italic bold",
}
}
}

View File

@@ -1,9 +0,0 @@
[
"class", "function.member.declaration",
"parameterType.declaration", "type", "parameterType.declaration", "type",
"variable.declaration", "parameterNames",
"function.member.declaration",
"interface.declaration",
"function.member.declaration", "testToken.testModifier"
]

View File

@@ -1,9 +0,0 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out"
},
"include": [
"src/**/*"
]
}

File diff suppressed because it is too large Load Diff