diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index db6524be03b..e527b8b134f 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -116,6 +116,11 @@ steps: yarn gulp minify-vscode-reh-web displayName: Compile condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) - script: | set -e diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 59bf1a250f6..96ef00c0657 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -74,6 +74,7 @@ function compileTask(src, out, build) { if (src === 'src') { generator.execute(); } + generateGitHubAuthConfig(); return srcPipe .pipe(generator.stream) .pipe(compile()) @@ -96,6 +97,17 @@ function watchTask(out, build) { } exports.watchTask = watchTask; const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); +function generateGitHubAuthConfig() { + const schemes = ['OSS', 'INSIDERS']; + let content = {}; + schemes.forEach(scheme => { + content[scheme] = { + id: process.env[`${scheme}_GITHUB_ID`], + secret: process.env[`${scheme}_GITHUB_SECRET`] + }; + }); + fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content)); +} class MonacoGenerator { constructor(isWatch) { this._executeSoonTimer = null; diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 578fae31a19..0d225d89972 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -88,6 +88,8 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod generator.execute(); } + generateGitHubAuthConfig(); + return srcPipe .pipe(generator.stream) .pipe(compile()) @@ -115,6 +117,19 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); +function generateGitHubAuthConfig() { + const schemes = ['OSS', 'INSIDERS']; + let content: { [key: string]: { id?: string, secret?: string }} = {}; + schemes.forEach(scheme => { + content[scheme] = { + id: process.env[`${scheme}_GITHUB_ID`], + secret: process.env[`${scheme}_GITHUB_SECRET`] + }; + }); + + fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content)); +} + class MonacoGenerator { private readonly _isWatch: boolean; public readonly stream: NodeJS.ReadWriteStream; diff --git a/build/package.json b/build/package.json index 6f9ebb5b34b..d1cd1b063b7 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.8.1-rc", + "typescript": "3.8.2", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index ee1e7bfb294..53bc78757a8 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2453,16 +2453,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" +typescript@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== + typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== - typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/extensions/github-authentication/.gitignore b/extensions/github-authentication/.gitignore new file mode 100644 index 00000000000..eab338cd463 --- /dev/null +++ b/extensions/github-authentication/.gitignore @@ -0,0 +1 @@ +src/common/config.json diff --git a/extensions/github-authentication/README.md b/extensions/github-authentication/README.md new file mode 100644 index 00000000000..755e5020961 --- /dev/null +++ b/extensions/github-authentication/README.md @@ -0,0 +1,7 @@ +# GitHub Authentication for Visual Studio Code + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +This extension provides support for authenticating to GitHub. diff --git a/extensions/github-authentication/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js new file mode 100644 index 00000000000..a513ac5c3b5 --- /dev/null +++ b/extensions/github-authentication/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts', + }, + externals: { + 'keytar': 'commonjs keytar' + } +}); diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json new file mode 100644 index 00000000000..8060b63f2f5 --- /dev/null +++ b/extensions/github-authentication/package.json @@ -0,0 +1,32 @@ +{ + "name": "github-authentication", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "version": "0.0.1", + "engines": { + "vscode": "^1.41.0" + }, + "enableProposedApi": true, + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "gulp compile-extension:github-authentication", + "watch": "gulp watch-extension:github-authentication" + }, + "dependencies": { + "uuid": "^3.3.3" + }, + "devDependencies": { + "@types/keytar": "^4.4.2", + "@types/node": "^10.12.21", + "@types/uuid": "^3.4.6", + "typescript": "^3.7.5" + } +} diff --git a/extensions/github-authentication/package.nls.json b/extensions/github-authentication/package.nls.json new file mode 100644 index 00000000000..592a413b9a5 --- /dev/null +++ b/extensions/github-authentication/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "GitHub Authentication", + "description": "GitHub Authentication Provider" +} diff --git a/extensions/github-authentication/src/common/clientRegistrar.ts b/extensions/github-authentication/src/common/clientRegistrar.ts new file mode 100644 index 00000000000..b4de0b8138a --- /dev/null +++ b/extensions/github-authentication/src/common/clientRegistrar.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ClientDetails { + id?: string; + secret?: string; +} + +export interface ClientConfig { + OSS: ClientDetails; + INSIDERS: ClientDetails; +} + +export class Registrar { + private _config: ClientConfig; + + constructor() { + try { + this._config = require('./config.json') as ClientConfig; + } catch (e) { + this._config = { + OSS: {}, + INSIDERS: {} + }; + } + } + getClientDetails(product: string): ClientDetails { + let details: ClientDetails | undefined; + switch (product) { + case 'code-oss': + details = this._config.OSS; + break; + + case 'vscode-insiders': + details = this._config.INSIDERS; + break; + + default: + throw new Error(`Unrecognized product ${product}`); + } + + if (!details.id || !details.secret) { + throw new Error(`No client configuration available for ${product}`); + } + + return details; + } +} + +const ClientRegistrar = new Registrar(); +export default ClientRegistrar; diff --git a/extensions/github-authentication/src/common/keychain.ts b/extensions/github-authentication/src/common/keychain.ts new file mode 100644 index 00000000000..c687febb442 --- /dev/null +++ b/extensions/github-authentication/src/common/keychain.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// keytar depends on a native module shipped in vscode, so this is +// how we load it +import * as keytarType from 'keytar'; +import { env } from 'vscode'; +import Logger from './logger'; + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; + +const SERVICE_ID = `${env.uriScheme}-github.login`; +const ACCOUNT_ID = 'account'; + +export class Keychain { + private keytar: Keytar; + + constructor() { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + } + + async setToken(token: string): Promise { + try { + return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); + } catch (e) { + // Ignore + Logger.error(`Setting token failed: ${e}`); + } + } + + async getToken(): Promise { + try { + return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Getting token failed: ${e}`); + return Promise.resolve(undefined); + } + } + + async deleteToken(): Promise { + try { + return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Deleting token failed: ${e}`); + return Promise.resolve(undefined); + } + } +} + +export const keychain = new Keychain(); diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts new file mode 100644 index 00000000000..90b3be8c97c --- /dev/null +++ b/extensions/github-authentication/src/common/logger.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +type LogLevel = 'Trace' | 'Info' | 'Error'; + +class Log { + private output: vscode.OutputChannel; + + constructor() { + this.output = vscode.window.createOutputChannel('GitHub Authentication'); + } + + private data2String(data: any): string { + if (data instanceof Error) { + return data.stack || data.message; + } + if (data.success === false && data.message) { + return data.message; + } + return data.toString(); + } + + public info(message: string, data?: any): void { + this.logLevel('Info', message, data); + } + + public error(message: string, data?: any): void { + this.logLevel('Error', message, data); + } + + public logLevel(level: LogLevel, message: string, data?: any): void { + this.output.appendLine(`[${level} - ${this.now()}] ${message}`); + if (data) { + this.output.appendLine(this.data2String(data)); + } + } + + private now(): string { + const now = new Date(); + return padLeft(now.getUTCHours() + '', 2, '0') + + ':' + padLeft(now.getMinutes() + '', 2, '0') + + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); + } +} + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s; +} + +const Logger = new Log(); +export default Logger; diff --git a/extensions/github-authentication/src/common/utils.ts b/extensions/github-authentication/src/common/utils.ts new file mode 100644 index 00000000000..6b18a9221db --- /dev/null +++ b/extensions/github-authentication/src/common/utils.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Disposable } from 'vscode'; + +export function filterEvent(event: Event, filter: (e: T) => boolean): Event { + return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); +} + +export function onceEvent(event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + const result = event(e => { + result.dispose(); + return listener.call(thisArgs, e); + }, null, disposables); + + return result; + }; +} + + +export interface PromiseAdapter { + ( + value: T, + resolve: + (value?: U | PromiseLike) => void, + reject: + (reason: any) => void + ): any; +} + +const passthrough = (value: any, resolve: (value?: any) => void) => resolve(value); + +/** + * Return a promise that resolves with the next emitted event, or with some future + * event as decided by an adapter. + * + * If specified, the adapter is a function that will be called with + * `(event, resolve, reject)`. It will be called once per event until it resolves or + * rejects. + * + * The default adapter is the passthrough function `(value, resolve) => resolve(value)`. + * + * @param event the event + * @param adapter controls resolution of the returned promise + * @returns a promise that resolves or rejects as specified by the adapter + */ +export async function promiseFromEvent( + event: Event, + adapter: PromiseAdapter = passthrough): Promise { + let subscription: Disposable; + return new Promise((resolve, reject) => + subscription = event((value: T) => { + try { + Promise.resolve(adapter(value, resolve, reject)) + .catch(reject); + } catch (error) { + reject(error); + } + }) + ).then( + (result: U) => { + subscription.dispose(); + return result; + }, + error => { + subscription.dispose(); + throw error; + } + ); +} diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts new file mode 100644 index 00000000000..943db6f20b5 --- /dev/null +++ b/extensions/github-authentication/src/extension.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { GitHubAuthenticationProvider, onDidChangeSessions } from './github'; +import { uriHandler } from './githubServer'; +import Logger from './common/logger'; + +export async function activate(context: vscode.ExtensionContext) { + + context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); + const loginService = new GitHubAuthenticationProvider(); + + await loginService.initialize(); + + vscode.authentication.registerAuthenticationProvider({ + id: 'GitHub', + displayName: 'GitHub', + onDidChangeSessions: onDidChangeSessions.event, + getSessions: () => Promise.resolve(loginService.sessions), + login: async (scopes: string[]) => { + try { + const session = await loginService.login(scopes.join(' ')); + Logger.info('Login success!'); + return session; + } catch (e) { + vscode.window.showErrorMessage(`Sign in failed: ${e}`); + Logger.error(e); + throw e; + } + }, + logout: async (id: string) => { + return loginService.logout(id); + } + }); + + return; +} + +// this method is called when your extension is deactivated +export function deactivate() { } diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts new file mode 100644 index 00000000000..fe4dcaa6d1f --- /dev/null +++ b/extensions/github-authentication/src/github.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { keychain } from './common/keychain'; +import { GitHubServer } from './githubServer'; +import Logger from './common/logger'; + +export const onDidChangeSessions = new vscode.EventEmitter(); + +export class GitHubAuthenticationProvider { + private _sessions: vscode.AuthenticationSession[] = []; + private _githubServer = new GitHubServer(); + + public async initialize(): Promise { + this._sessions = await this.readSessions(); + this.pollForChange(); + } + + private pollForChange() { + setTimeout(async () => { + const storedSessions = await this.readSessions(); + let didChange = false; + + storedSessions.forEach(session => { + 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) { + this._sessions.push(session); + didChange = true; + } + }); + + this._sessions.map(session => { + const matchesExisting = storedSessions.some(s => s.id === session.id); + // Another window has logged out, remove from our state + if (!matchesExisting) { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1); + } + + didChange = true; + } + }); + + if (didChange) { + onDidChangeSessions.fire(); + } + + this.pollForChange(); + }, 1000 * 30); + } + + private async readSessions(): Promise { + const storedSessions = await keychain.getToken(); + if (storedSessions) { + try { + return JSON.parse(storedSessions); + } catch (e) { + Logger.error(`Error reading sessions: ${e}`); + } + } + + return []; + } + + private async storeSessions(): Promise { + await keychain.setToken(JSON.stringify(this._sessions)); + } + + get sessions(): vscode.AuthenticationSession[] { + return this._sessions; + } + + public async login(scopes: string): Promise { + const token = await this._githubServer.login(scopes); + const session = await this.tokenToSession(token, scopes.split(' ')); + await this.setToken(session); + return session; + } + + private async tokenToSession(token: string, scopes: string[]): Promise { + const userInfo = await this._githubServer.getUserInfo(token); + return { + id: userInfo.id, + accessToken: () => Promise.resolve(token), + accountName: userInfo.accountName, + scopes: scopes + }; + } + private async setToken(session: vscode.AuthenticationSession): Promise { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1, session); + } else { + this._sessions.push(session); + } + + this.storeSessions(); + } + + public async logout(id: string) { + const sessionIndex = this._sessions.findIndex(session => session.id === id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1); + } + + this.storeSessions(); + } +} diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts new file mode 100644 index 00000000000..6c84738261d --- /dev/null +++ b/extensions/github-authentication/src/githubServer.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as https from 'https'; +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'; + +class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } +} + +export const uriHandler = new UriEventHandler; + +const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter = + (state, clientDetails) => async (uri, resolve, reject) => { + Logger.info('Exchanging code for token...'); + const query = parseQuery(uri); + const code = query.code; + + if (query.state !== state) { + reject('Received mismatched state'); + return; + } + + 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}`, + method: 'POST', + headers: { + Accept: 'application/json' + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + Logger.info('Token exchange success!'); + resolve(json.access_token); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }; + +function parseQuery(uri: vscode.Uri) { + return uri.query.split('&').reduce((prev: any, current) => { + const queryString = current.split('='); + prev[queryString[0]] = queryString[1]; + return prev; + }, {}); +} + +export class GitHubServer { + public async login(scopes: string): Promise { + Logger.info('Logging in...'); + const state = uuid(); + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const clientDetails = ClientRegistrar.getClientDetails(callbackUri.scheme); + const 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)); + } + + public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> { + return new Promise((resolve, reject) => { + Logger.info('Getting account info...'); + const post = https.request({ + host: 'api.github.com', + path: `/user`, + method: 'GET', + headers: { + Authorization: `token ${token}`, + 'User-Agent': 'Visual-Studio-Code' + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + Logger.info('Got account info!'); + resolve({ id: json.id, accountName: json.login }); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } +} diff --git a/extensions/github-authentication/src/typings/ref.d.ts b/extensions/github-authentication/src/typings/ref.d.ts new file mode 100644 index 00000000000..c9849d48e08 --- /dev/null +++ b/extensions/github-authentication/src/typings/ref.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json new file mode 100644 index 00000000000..1225709307b --- /dev/null +++ b/extensions/github-authentication/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../shared.tsconfig.json", + "compilerOptions": { + "outDir": "./out", + "experimentalDecorators": true, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock new file mode 100644 index 00000000000..dcab74a1fea --- /dev/null +++ b/extensions/github-authentication/yarn.lock @@ -0,0 +1,454 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/keytar@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" + integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw== + dependencies: + keytar "*" + +"@types/node@^10.12.21": + version "10.17.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541" + integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw== + +"@types/uuid@^3.4.6": + version "3.4.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.7.tgz#51d42247473bc00e38cc8dfaf70d936842a36c03" + integrity sha512-C2j2FWgQkF1ru12SjZJyMaTPxs/f6n90+5G5qNakBxKXjTBc/YTSelHh4Pz1HUDwxFXD9WvpQhOGCDC+/Y4mIQ== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + +chownr@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +keytar@*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.1.0.tgz#d572ed9250ff2b4c8d729621397e00b17bfa5581" + integrity sha512-SptCrRDqLbTeOMB2Z9UmVOS+OKguIrMft+EUaCB8xJPiFMjy6Jnmjgv/LA0rg1ENgLelzwSsC5PSQXF0uoqNDQ== + dependencies: + nan "2.14.0" + prebuild-install "5.3.3" + +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +nan@2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +napi-build-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" + integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== + +node-abi@^2.7.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.14.0.tgz#24650e24e8ffad2b61352519263f0cf4e2ddbfe9" + integrity sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg== + dependencies: + semver "^5.4.1" + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +npmlog@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +prebuild-install@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" + integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.1, readable-stream@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" + integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index dfceebce8e7..8a12ff009c3 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -100,27 +100,29 @@ }, "configurationDefaults": { "[json]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" }, "[jsonc]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" - } + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } }, - "jsonValidation": [{ - "fileMatch": "*.schema.json", - "url": "http://json-schema.org/draft-07/schema#" - }] + "jsonValidation": [ + { + "fileMatch": "*.schema.json", + "url": "http://json-schema.org/draft-07/schema#" + } + ] }, "dependencies": { "request-light": "^0.2.5", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.1", + "vscode-languageclient": "^6.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index a21b05e4aba..ae5b4a9e073 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,10 +12,10 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.2.0", + "jsonc-parser": "^2.2.1", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.12", - "vscode-languageserver": "^6.0.1", + "vscode-json-languageservice": "^3.5.1", + "vscode-languageserver": "^6.1.1", "vscode-uri": "^2.1.1" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index f700170b41d..4f40bd5a3fd 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -450,7 +450,7 @@ connection.onDocumentRangeFormatting((formatParams, token) => { const edits = languageService.format(document, formatParams.range, formatParams.options); if (edits.length > formatterMaxNumberOfEdits) { const newText = TextDocument.applyEdits(document, edits); - return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length - 1)), newText)]; + return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length)), newText)]; } return edits; } diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 9c72cea8b07..f6b78986f8f 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -61,10 +61,10 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== ms@2.0.0: version "2.0.0" @@ -80,14 +80,14 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.12: - version "3.4.12" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.12.tgz#e7c96a1824896a624cc7bb14f46fbf9cb7e6c5a3" - integrity sha512-+tA0KPVM1pDfORZqsQen7bY5buBpQGDTVYEobm5MoGtXNeZY2Kn0iy5wIQqXveb28LRv/I5xKE87dmNJTEaijQ== +vscode-json-languageservice@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.1.tgz#75779d466107cbc8c4cc9828df100df71c1870f8" + integrity sha512-F8jPqcAC1mbQOMKvGYS4dGEw9JCZxVEi7tc5ASZLfcfwKq2URZKB4fOtdy1GEsTLsrW11tVrBjEPntpXzqp/NA== dependencies: - jsonc-parser "^2.2.0" - vscode-languageserver-textdocument "^1.0.1-next.1" - vscode-languageserver-types "^3.15.0" + jsonc-parser "^2.2.1" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.15.1" vscode-nls "^4.1.1" vscode-uri "^2.1.1" @@ -96,30 +96,30 @@ 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-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-textdocument@^1.0.1-next.1: - version "1.0.1-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1-next.1.tgz#c8f2f792c7c88d33ea8441ca04bfb8376896b671" - integrity sha512-Cmt0KsNxouns+d7/Kw/jWtWU9Z3h56z1qAA8utjDOEqrDcrTs2rDXv3EJRa99nuKM3wVf6DbWym1VqL9q71XPA== +vscode-languageserver-textdocument@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" + integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== -vscode-languageserver-types@3.15.0, vscode-languageserver-types@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== -vscode-languageserver@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.1.tgz#4f499d245f1baf83bd607dd79c4c3fd19e8cefc0" - integrity sha512-Wk4I/Dn5KNARWockdCrYuuImJz6bpYG8n2G3Kk5AU6Xy9nWNHD6YjB9/Rd99p4goViZOyETM+hYE81LnEzQZUA== +vscode-languageserver@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" + integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== dependencies: - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 608f3eaed5b..cbda171084d 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -125,26 +125,26 @@ 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.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.1.tgz#acd138e0a19a40c5788365e882ae11c164d9a460" - integrity sha512-7yZaSHichTJEyOJykI2RLQEECf9MqNLoklzC/1OVi/M8ioIsWQ1+lkN1nTsUhd6+F7p9ar9dNmPiEhL0i5uUBA== +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== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" -vscode-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-types@3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/package.json b/extensions/package.json index df820979f1a..b8e304b2343 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^3.8.1-rc" + "typescript": "3.8.2" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 649ec902e03..207ae27aa1b 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -12,14 +12,14 @@ "contributes": { "languages": [{ "id": "shellscript", - "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh"], - "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh"], + "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh", "csh"], + "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh", ".csh", ".cshrc"], "filenames": [ "APKBUILD", "PKGBUILD", ".envrc" ], - "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh|csh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": ["text/x-shellscript"] }], diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 5652967abfc..6e926eb8d7e 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,11 +1,2 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; - -declare module "typescript/lib/protocol" { - // TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types - interface Response { - performanceData?: { - updateGraphDurationMs?: number; - } - } -} diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts index 99d13d8181e..6f4c8f6bf35 100644 --- a/extensions/typescript-language-features/src/tsServer/serverError.ts +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -20,7 +20,7 @@ export class TypeScriptServerError extends Error { private constructor( serverId: string, - version: TypeScriptVersion, + public readonly version: TypeScriptVersion, private readonly response: Proto.Response, public readonly serverMessage: string | undefined, public readonly serverStack: string | undefined diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index afd6e382cc5..6be28453e0e 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -529,7 +529,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType id: MessageAction; } - const previousVersion = this.apiVersion; const previousState = this.serverState; this.serverState = ServerState.None; @@ -570,7 +569,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType prompt.then(item => { if (item?.id === MessageAction.reportIssue) { const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError - ? getReportIssueArgsForError(previousState.error, previousVersion) + ? getReportIssueArgsForError(previousState.error) : undefined; return vscode.commands.executeCommand('workbench.action.openIssueReporter', args); } @@ -875,7 +874,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } } -function getReportIssueArgsForError(error: TypeScriptServerError, apiVersion: API): { issueTitle: string, issueBody: string } | undefined { +function getReportIssueArgsForError(error: TypeScriptServerError): { issueTitle: string, issueBody: string } | undefined { if (!error.serverStack || !error.serverMessage) { return undefined; } @@ -885,7 +884,7 @@ function getReportIssueArgsForError(error: TypeScriptServerError, apiVersion: AP return { issueTitle: `TS Server fatal error: ${error.serverMessage}`, - issueBody: `**TypeScript Version:** ${apiVersion.fullVersionString} + issueBody: `**TypeScript Version:** ${error.version.apiVersion?.fullVersionString} **Steps to reproduce crash** @@ -893,7 +892,7 @@ function getReportIssueArgsForError(error: TypeScriptServerError, apiVersion: AP 2. 3. -** TS Server Error Stack ** +**TS Server Error Stack** \`\`\` ${error.serverStack} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 1d6447cfe6d..43a70c058c2 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== +typescript@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== diff --git a/package.json b/package.json index 69c328dd74f..f1a86363326 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "ts-loader": "^4.4.2", - "typescript": "^3.8.1-rc", + "typescript": "3.8.2", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/src/main.js b/src/main.js index 6a6cb060acc..f8aa90f9c8c 100644 --- a/src/main.js +++ b/src/main.js @@ -131,8 +131,12 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering' + 'disable-color-correct-rendering', + + // override for the color profile to use + 'force-color-profile' ]; + if (process.platform === 'linux') { SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); } @@ -147,7 +151,16 @@ function configureCommandlineSwitchesSync(cliArgs) { } const argvValue = argvConfig[argvKey]; - if (argvValue === true || argvValue === 'true') { + + // Color profile + if (argvKey === 'force-color-profile') { + if (argvValue) { + app.commandLine.appendSwitch(argvKey, argvValue); + } + } + + // Others + else if (argvValue === true || argvValue === 'true') { if (argvKey === 'disable-hardware-acceleration') { app.disableHardwareAcceleration(); // needs to be called explicitly } else { diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 907dc51ff93..3ca76db9a29 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -283,3 +283,20 @@ export function isRootOrDriveLetter(path: string): boolean { return pathNormalized === posix.sep; } + +export function indexOfPath(path: string, candidate: string, ignoreCase: boolean): number { + if (candidate.length > path.length) { + return -1; + } + + if (path === candidate) { + return 0; + } + + if (ignoreCase) { + path = path.toLowerCase(); + candidate = candidate.toLowerCase(); + } + + return path.indexOf(candidate); +} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 460e1a910ae..980c686c9ad 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI, UriComponents } from 'vs/base/common/uri'; + const _typeof = { number: 'number', string: 'string', @@ -262,3 +264,21 @@ export type AddFirstParameterToFunctions = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto }; + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents and + * drops all functions. + * todo@joh use toJSON-results + */ +export type Dto = { [K in keyof T]: T[K] extends URI + ? UriComponents + : T[K] extends Function + ? never + : UriDto }; diff --git a/src/vs/base/parts/composite/browser/compositeDnd.ts b/src/vs/base/parts/composite/browser/compositeDnd.ts new file mode 100644 index 00000000000..ca8b52d491a --- /dev/null +++ b/src/vs/base/parts/composite/browser/compositeDnd.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDragAndDropData } from 'vs/base/browser/dnd'; + +export class CompositeDragAndDropData implements IDragAndDropData { + constructor(private type: 'view' | 'composite', private id: string) { } + update(dataTransfer: DataTransfer): void { + // no-op + } + getData(): { + type: 'view' | 'composite'; + id: string; + } { + return { type: this.type, id: this.id }; + } +} + +export interface ICompositeDragAndDrop { + drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void; + onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean; +} diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 059eb29406d..afb94d70395 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -575,10 +575,6 @@ class QuickPick extends QuickInput implements IQuickPi return this.visible ? this.ui.inputBox.hasFocus() : false; } - public focusOnInput() { - this.ui.inputBox.setFocus(); - } - onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 5640fecc09b..8cbf10553e7 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -209,8 +209,6 @@ export interface IQuickPick extends IQuickInput { validationMessage: string | undefined; inputHasFocus(): boolean; - - focusOnInput(): void; } export interface IInputBox extends IQuickInput { diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 13ec59bc49f..567b9cdc08a 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -25,6 +25,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -1529,6 +1530,102 @@ export namespace CoreNavigationCommands { }); } +/** + * A command that will: + * 1. invoke a command on the focused editor. + * 2. otherwise, invoke a browser built-in command on the `activeElement`. + * 3. otherwise, invoke a command on the workbench active editor. + */ +abstract class EditorOrNativeTextInputCommand extends Command { + + public runCommand(accessor: ServicesAccessor, args: any): void { + + const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + // Only if editor text focus (i.e. not if editor has widget focus). + if (focusedEditor && focusedEditor.hasTextFocus()) { + return this.runEditorCommand(accessor, focusedEditor, args); + } + + // Ignore this action when user is focused on an element that allows for entering text + const activeElement = document.activeElement; + if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + return this.runDOMCommand(); + } + + // Redirecting to active editor + const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + if (activeEditor) { + activeEditor.focus(); + return this.runEditorCommand(accessor, activeEditor, args); + } + } + + public abstract runDOMCommand(): void; + public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void; +} + +class SelectAllCommand extends EditorOrNativeTextInputCommand { + constructor() { + super({ + id: 'editor.action.selectAll', + precondition: EditorContextKeys.textInputFocus, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: null, + primary: KeyMod.CtrlCmd | KeyCode.KEY_A + }, + menuOpts: [{ + menuId: MenuId.MenubarSelectionMenu, + group: '1_basic', + title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), + order: 1 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('selectAll', "Select All"), + order: 1 + }] + }); + } + public runDOMCommand(): void { + document.execCommand('selectAll'); + } + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + args = args || {}; + args.source = 'keyboard'; + CoreNavigationCommands.SelectAll.runEditorCommand(accessor, editor, args); + } +} + +class UndoCommand extends EditorOrNativeTextInputCommand { + public runDOMCommand(): void { + document.execCommand('undo'); + } + public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void { + if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { + return; + } + editor.getModel().undo(); + } +} + +class RedoCommand extends EditorOrNativeTextInputCommand { + public runDOMCommand(): void { + document.execCommand('redo'); + } + public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void { + if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { + return; + } + editor.getModel().redo(); + } +} + +function registerCommand(command: T): T { + command.register(); + return command; +} + export namespace CoreEditingCommands { export abstract class CoreEditingCommand extends EditorCommand { @@ -1659,62 +1756,53 @@ export namespace CoreEditingCommands { } }); -} + export const Undo: UndoCommand = registerCommand(new UndoCommand({ + id: 'undo', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z + }, + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '1_do', + title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), + order: 1 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('undo', "Undo"), + order: 1 + }] + })); -function registerCommand(command: Command) { - command.register(); -} + export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable })); -/** - * A command that will: - * 1. invoke a command on the focused editor. - * 2. otherwise, invoke a browser built-in command on the `activeElement`. - * 3. otherwise, invoke a command on the workbench active editor. - */ -class EditorOrNativeTextInputCommand extends Command { + export const Redo: RedoCommand = registerCommand(new RedoCommand({ + id: 'redo', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } + }, + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '1_do', + title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), + order: 2 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('redo', "Redo"), + order: 1 + }] + })); - private readonly _editorHandler: string | EditorCommand; - private readonly _inputHandler: string; - - constructor(opts: ICommandOptions & { editorHandler: string | EditorCommand; inputHandler: string; }) { - super(opts); - this._editorHandler = opts.editorHandler; - this._inputHandler = opts.inputHandler; - } - - public runCommand(accessor: ServicesAccessor, args: any): void { - - const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - // Only if editor text focus (i.e. not if editor has widget focus). - if (focusedEditor && focusedEditor.hasTextFocus()) { - return this._runEditorHandler(accessor, focusedEditor, args); - } - - // Ignore this action when user is focused on an element that allows for entering text - const activeElement = document.activeElement; - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - document.execCommand(this._inputHandler); - return; - } - - // Redirecting to active editor - const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); - if (activeEditor) { - activeEditor.focus(); - return this._runEditorHandler(accessor, activeEditor, args); - } - } - - private _runEditorHandler(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - const HANDLER = this._editorHandler; - if (typeof HANDLER === 'string') { - editor.trigger('keyboard', HANDLER, args); - } else { - args = args || {}; - args.source = 'keyboard'; - HANDLER.runEditorCommand(accessor, editor, args); - } - } + export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable })); } /** @@ -1743,78 +1831,7 @@ class EditorHandlerCommand extends Command { } } -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: CoreNavigationCommands.SelectAll, - inputHandler: 'selectAll', - id: 'editor.action.selectAll', - precondition: EditorContextKeys.textInputFocus, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: null, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A - }, - menuOpts: [{ - menuId: MenuId.MenubarSelectionMenu, - group: '1_basic', - title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), - order: 1 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('selectAll', "Select All"), - order: 1 - }] -})); - -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: Handler.Undo, - inputHandler: 'undo', - id: Handler.Undo, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z - }, - menuOpts: [{ - menuId: MenuId.MenubarEditMenu, - group: '1_do', - title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), - order: 1 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('undo', "Undo"), - order: 1 - }] -})); -registerCommand(new EditorHandlerCommand('default:' + Handler.Undo, Handler.Undo)); - -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: Handler.Redo, - inputHandler: 'redo', - id: Handler.Redo, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } - }, - menuOpts: [{ - menuId: MenuId.MenubarEditMenu, - group: '1_do', - title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), - order: 2 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('redo', "Redo"), - order: 1 - }] -})); -registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo)); +registerCommand(new SelectAllCommand()); function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void { registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId)); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e5c63003465..5262d8003b9 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1543,11 +1543,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; } + const onDidChangeTextFocus = (textFocus: boolean) => { + if (this._modelData) { + this._modelData.cursor.setHasFocus(textFocus); + } + this._editorTextFocus.setValue(textFocus); + }; + const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e); viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); - viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true); - viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false); + viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true); + viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false); viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e); viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.fire(e); diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 82204018f2c..cd488b900ac 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -16,7 +16,7 @@ import { Range, IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; -import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; +import { RawContentChangedType, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { dispose } from 'vs/base/common/lifecycle'; @@ -186,6 +186,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { public context: CursorContext; private _cursors: CursorCollection; + private _hasFocus: boolean; private _isHandling: boolean; private _isDoingComposition: boolean; private _selectionsWhenCompositionStarted: Selection[] | null; @@ -202,6 +203,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this.context = new CursorContext(this._configuration, this._model, this._viewModel); this._cursors = new CursorCollection(this.context); + this._hasFocus = false; this._isHandling = false; this._isDoingComposition = false; this._selectionsWhenCompositionStarted = null; @@ -215,8 +217,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { return; } - let hadFlushEvent = e.containsEvent(RawContentChangedType.Flush); - this._onModelContentChanged(hadFlushEvent); + this._onModelContentChanged(e); })); this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => { @@ -264,6 +265,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { super.dispose(); } + public setHasFocus(hasFocus: boolean): void { + this._hasFocus = hasFocus; + } + private _validateAutoClosedActions(): void { if (this._autoClosedActions.length > 0) { let selections: Range[] = this._cursors.getSelections(); @@ -392,8 +397,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this.reveal('restoreState', true, RevealTarget.Primary, editorCommon.ScrollType.Immediate); } - private _onModelContentChanged(hadFlushEvent: boolean): void { + private _onModelContentChanged(e: ModelRawContentChangedEvent): void { + const hadFlushEvent = e.containsEvent(RawContentChangedType.Flush); this._prevEditOperationType = EditOperationType.Other; if (hadFlushEvent) { @@ -403,8 +409,13 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._validateAutoClosedActions(); this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null); } else { - const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); - this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); + if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) { + const cursorState = CursorState.fromModelSelections(e.resultingSelection); + this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState); + } else { + const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); + this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); + } } } @@ -704,11 +715,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { const oldState = new CursorModelState(this._model, this); let cursorChangeReason = CursorChangeReason.NotSet; - if (handlerId !== H.Undo && handlerId !== H.Redo) { - // TODO@Alex: if the undo/redo stack contains non-null selections - // it would also be OK to stop tracking selections here - this._cursors.stopTrackingSelections(); - } + this._cursors.stopTrackingSelections(); // ensure valid state on all cursors this._cursors.ensureValidState(); @@ -734,16 +741,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._cut(); break; - case H.Undo: - cursorChangeReason = CursorChangeReason.Undo; - this._interpretCommandResult(this._model.undo()); - break; - - case H.Redo: - cursorChangeReason = CursorChangeReason.Redo; - this._interpretCommandResult(this._model.redo()); - break; - case H.ExecuteCommand: this._externalExecuteCommand(payload); break; @@ -762,9 +759,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._isHandling = false; - if (handlerId !== H.Undo && handlerId !== H.Redo) { - this._cursors.startTrackingSelections(); - } + this._cursors.startTrackingSelections(); this._validateAutoClosedActions(); diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index fca67398738..e176e90ede3 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -678,9 +678,5 @@ export const Handler = { CompositionStart: 'compositionStart', CompositionEnd: 'compositionEnd', Paste: 'paste', - Cut: 'cut', - - Undo: 'undo', - Redo: 'redo', }; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 566aca3d7a1..644a224686f 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -379,6 +379,13 @@ export interface IValidEditOperation { forceMoveMarkers: boolean; } +/** + * @internal + */ +export interface IValidEditOperations { + operations: IValidEditOperation[]; +} + /** * A callback that can compute the cursor state after applying a series of edit operations. */ @@ -1086,18 +1093,28 @@ export interface ITextModel { */ applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; + /** + * @internal + */ + _applyEdits(edits: IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[]; + /** * Change the end of line sequence without recording in the undo stack. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way. */ setEOL(eol: EndOfLineSequence): void; + /** + * @internal + */ + _setEOL(eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void; + /** * Undo edit operations until the first previous stop point created by `pushStackElement`. * The inverse edit operations will be pushed on the redo stack. * @internal */ - undo(): Selection[] | null; + undo(): void; /** * Is there anything in the undo stack? @@ -1110,7 +1127,7 @@ export interface ITextModel { * The inverse edit operations will be pushed on the undo stack. * @internal */ - redo(): Selection[] | null; + redo(): void; /** * Is there anything in the redo stack? diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index a870a0822d1..1e6c83563d8 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -3,61 +3,72 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Selection } from 'vs/editor/common/core/selection'; -import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; +import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { IUndoRedoService, IUndoRedoElement, IUndoRedoContext } from 'vs/platform/undoRedo/common/undoRedo'; +import { URI } from 'vs/base/common/uri'; -interface IEditOperation { - operations: IValidEditOperation[]; -} +class EditStackElement implements IUndoRedoElement { -interface IStackElement { - readonly beforeVersionId: number; - readonly beforeCursorState: Selection[] | null; - readonly afterCursorState: Selection[] | null; - readonly afterVersionId: number; + public readonly label: string; + private _isOpen: boolean; + private readonly _model: TextModel; + private readonly _beforeVersionId: number; + private readonly _beforeCursorState: Selection[]; + private _afterVersionId: number; + private _afterCursorState: Selection[] | null; + private _edits: IValidEditOperations[]; - undo(model: TextModel): void; - redo(model: TextModel): void; -} - -class EditStackElement implements IStackElement { - public readonly beforeVersionId: number; - public readonly beforeCursorState: Selection[]; - public afterCursorState: Selection[] | null; - public afterVersionId: number; - - public editOperations: IEditOperation[]; - - constructor(beforeVersionId: number, beforeCursorState: Selection[]) { - this.beforeVersionId = beforeVersionId; - this.beforeCursorState = beforeCursorState; - this.afterCursorState = null; - this.afterVersionId = -1; - this.editOperations = []; + public get resources(): readonly URI[] { + return [this._model.uri]; } - public undo(model: TextModel): void { - // Apply all operations in reverse order - for (let i = this.editOperations.length - 1; i >= 0; i--) { - this.editOperations[i] = { - operations: model.applyEdits(this.editOperations[i].operations) - }; - } + constructor(model: TextModel, beforeVersionId: number, beforeCursorState: Selection[], afterVersionId: number, afterCursorState: Selection[] | null, operations: IValidEditOperation[]) { + this.label = nls.localize('edit', "Typing"); + this._isOpen = true; + this._model = model; + this._beforeVersionId = beforeVersionId; + this._beforeCursorState = beforeCursorState; + this._afterVersionId = afterVersionId; + this._afterCursorState = afterCursorState; + this._edits = [{ operations: operations }]; } - public redo(model: TextModel): void { - // Apply all operations - for (let i = 0; i < this.editOperations.length; i++) { - this.editOperations[i] = { - operations: model.applyEdits(this.editOperations[i].operations) - }; - } + public isOpen(): boolean { + return this._isOpen; + } + + public append(operations: IValidEditOperation[], afterVersionId: number, afterCursorState: Selection[] | null): void { + this._edits.push({ operations: operations }); + this._afterVersionId = afterVersionId; + this._afterCursorState = afterCursorState; + } + + public close(): void { + this._isOpen = false; + } + + undo(ctx: IUndoRedoContext): void { + this._isOpen = false; + this._edits.reverse(); + this._edits = this._model._applyEdits(this._edits, true, false, this._beforeVersionId, this._beforeCursorState); + } + + redo(ctx: IUndoRedoContext): void { + this._isOpen = false; + this._edits.reverse(); + this._edits = this._model._applyEdits(this._edits, false, true, this._afterVersionId, this._afterCursorState); + } + + invalidate(resource: URI): void { + // nothing to do } } -function getModelEOL(model: TextModel): EndOfLineSequence { +function getModelEOL(model: ITextModel): EndOfLineSequence { const eol = model.getEOL(); if (eol === '\n') { return EndOfLineSequence.LF; @@ -66,32 +77,40 @@ function getModelEOL(model: TextModel): EndOfLineSequence { } } -class EOLStackElement implements IStackElement { - public readonly beforeVersionId: number; - public readonly beforeCursorState: Selection[] | null; - public readonly afterCursorState: Selection[] | null; - public afterVersionId: number; +class EOLStackElement implements IUndoRedoElement { - public eol: EndOfLineSequence; + public readonly label: string; + private readonly _model: TextModel; + private readonly _beforeVersionId: number; + private readonly _afterVersionId: number; + private _eol: EndOfLineSequence; - constructor(beforeVersionId: number, setEOL: EndOfLineSequence) { - this.beforeVersionId = beforeVersionId; - this.beforeCursorState = null; - this.afterCursorState = null; - this.afterVersionId = -1; - this.eol = setEOL; + public get resources(): readonly URI[] { + return [this._model.uri]; } - public undo(model: TextModel): void { - let redoEOL = getModelEOL(model); - model.setEOL(this.eol); - this.eol = redoEOL; + constructor(model: TextModel, beforeVersionId: number, afterVersionId: number, eol: EndOfLineSequence) { + this.label = nls.localize('eol', "Change End Of Line Sequence"); + this._model = model; + this._beforeVersionId = beforeVersionId; + this._afterVersionId = afterVersionId; + this._eol = eol; } - public redo(model: TextModel): void { - let undoEOL = getModelEOL(model); - model.setEOL(this.eol); - this.eol = undoEOL; + undo(ctx: IUndoRedoContext): void { + const redoEOL = getModelEOL(this._model); + this._model._setEOL(this._eol, true, false, this._beforeVersionId, null); + this._eol = redoEOL; + } + + redo(ctx: IUndoRedoContext): void { + const undoEOL = getModelEOL(this._model); + this._model._setEOL(this._eol, false, true, this._afterVersionId, null); + this._eol = undoEOL; + } + + invalidate(resource: URI): void { + // nothing to do } } @@ -102,76 +121,52 @@ export interface IUndoRedoResult { export class EditStack { - private readonly model: TextModel; - private currentOpenStackElement: IStackElement | null; - private past: IStackElement[]; - private future: IStackElement[]; + private readonly _model: TextModel; + private readonly _undoRedoService: IUndoRedoService; - constructor(model: TextModel) { - this.model = model; - this.currentOpenStackElement = null; - this.past = []; - this.future = []; + constructor(model: TextModel, undoRedoService: IUndoRedoService) { + this._model = model; + this._undoRedoService = undoRedoService; } public pushStackElement(): void { - if (this.currentOpenStackElement !== null) { - this.past.push(this.currentOpenStackElement); - this.currentOpenStackElement = null; + const lastElement = this._undoRedoService.getLastElement(this._model.uri); + if (lastElement && lastElement instanceof EditStackElement) { + lastElement.close(); } } public clear(): void { - this.currentOpenStackElement = null; - this.past = []; - this.future = []; + this._undoRedoService.removeElements(this._model.uri); } public pushEOL(eol: EndOfLineSequence): void { - // No support for parallel universes :( - this.future = []; + const beforeVersionId = this._model.getAlternativeVersionId(); + const inverseEOL = getModelEOL(this._model); + this._model.setEOL(eol); + const afterVersionId = this._model.getAlternativeVersionId(); - if (this.currentOpenStackElement) { - this.pushStackElement(); + const lastElement = this._undoRedoService.getLastElement(this._model.uri); + if (lastElement && lastElement instanceof EditStackElement) { + lastElement.close(); } - - const prevEOL = getModelEOL(this.model); - let stackElement = new EOLStackElement(this.model.getAlternativeVersionId(), prevEOL); - - this.model.setEOL(eol); - - stackElement.afterVersionId = this.model.getVersionId(); - this.currentOpenStackElement = stackElement; - this.pushStackElement(); + this._undoRedoService.pushElement(new EOLStackElement(this._model, inverseEOL, beforeVersionId, afterVersionId)); } public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { - // No support for parallel universes :( - this.future = []; + const beforeVersionId = this._model.getAlternativeVersionId(); + const inverseEditOperations = this._model.applyEdits(editOperations); + const afterVersionId = this._model.getAlternativeVersionId(); + const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); - let stackElement: EditStackElement | null = null; - - if (this.currentOpenStackElement) { - if (this.currentOpenStackElement instanceof EditStackElement) { - stackElement = this.currentOpenStackElement; - } else { - this.pushStackElement(); - } + const lastElement = this._undoRedoService.getLastElement(this._model.uri); + if (lastElement && lastElement instanceof EditStackElement && lastElement.isOpen()) { + lastElement.append(inverseEditOperations, afterVersionId, afterCursorState); + } else { + this._undoRedoService.pushElement(new EditStackElement(this._model, beforeVersionId, beforeCursorState, afterVersionId, afterCursorState, inverseEditOperations)); } - if (!this.currentOpenStackElement) { - stackElement = new EditStackElement(this.model.getAlternativeVersionId(), beforeCursorState); - this.currentOpenStackElement = stackElement; - } - - const inverseEditOperation: IEditOperation = { - operations: this.model.applyEdits(editOperations) - }; - - stackElement!.editOperations.push(inverseEditOperation); - stackElement!.afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperation.operations); - stackElement!.afterVersionId = this.model.getVersionId(); - return stackElement!.afterCursorState; + return afterCursorState; } private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null { @@ -182,62 +177,4 @@ export class EditStack { return null; } } - - public undo(): IUndoRedoResult | null { - - this.pushStackElement(); - - if (this.past.length > 0) { - const pastStackElement = this.past.pop()!; - - try { - pastStackElement.undo(this.model); - } catch (e) { - onUnexpectedError(e); - this.clear(); - return null; - } - - this.future.push(pastStackElement); - - return { - selections: pastStackElement.beforeCursorState, - recordedVersionId: pastStackElement.beforeVersionId - }; - } - - return null; - } - - public canUndo(): boolean { - return (this.past.length > 0) || this.currentOpenStackElement !== null; - } - - public redo(): IUndoRedoResult | null { - - if (this.future.length > 0) { - const futureStackElement = this.future.pop()!; - - try { - futureStackElement.redo(this.model); - } catch (e) { - onUnexpectedError(e); - this.clear(); - return null; - } - - this.past.push(futureStackElement); - - return { - selections: futureStackElement.afterCursorState, - recordedVersionId: futureStackElement.afterVersionId - }; - } - - return null; - } - - public canRedo(): boolean { - return (this.future.length > 0); - } } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 68660134bf7..9c999e23ce5 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -36,6 +36,8 @@ import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 import { Color } from 'vs/base/common/color'; import { Constants } from 'vs/base/common/uint'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -188,7 +190,7 @@ export class TextModel extends Disposable implements model.ITextModel { }; public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel { - return new TextModel(text, options, languageIdentifier, uri); + return new TextModel(text, options, languageIdentifier, uri, new UndoRedoService()); } public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions { @@ -253,6 +255,7 @@ export class TextModel extends Disposable implements model.ITextModel { public readonly id: string; public readonly isForSimpleWidget: boolean; private readonly _associatedResource: URI; + private readonly _undoRedoService: IUndoRedoService; private _attachedEditorCount: number; private _buffer: model.ITextBuffer; private _options: model.TextModelResolvedOptions; @@ -268,7 +271,7 @@ export class TextModel extends Disposable implements model.ITextModel { private readonly _isTooLargeForTokenization: boolean; //#region Editing - private _commandManager: EditStack; + private readonly _commandManager: EditStack; private _isUndoing: boolean; private _isRedoing: boolean; private _trimAutoWhitespaceLines: number[] | null; @@ -293,7 +296,13 @@ export class TextModel extends Disposable implements model.ITextModel { private readonly _tokenization: TextModelTokenization; //#endregion - constructor(source: string | model.ITextBufferFactory, creationOptions: model.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier | null, associatedResource: URI | null = null) { + constructor( + source: string | model.ITextBufferFactory, + creationOptions: model.ITextModelCreationOptions, + languageIdentifier: LanguageIdentifier | null, + associatedResource: URI | null = null, + undoRedoService: IUndoRedoService + ) { super(); // Generate a new unique model id @@ -305,6 +314,7 @@ export class TextModel extends Disposable implements model.ITextModel { } else { this._associatedResource = associatedResource; } + this._undoRedoService = undoRedoService; this._attachedEditorCount = 0; this._buffer = createTextBuffer(source, creationOptions.defaultEOL); @@ -347,7 +357,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); - this._commandManager = new EditStack(this); + this._commandManager = new EditStack(this, undoRedoService); this._isUndoing = false; this._isRedoing = false; this._trimAutoWhitespaceLines = null; @@ -362,6 +372,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._onWillDispose.fire(); this._languageRegistryListener.dispose(); this._tokenization.dispose(); + this._undoRedoService.removeElements(this.uri); this._isDisposed = true; super.dispose(); this._isDisposing = false; @@ -436,7 +447,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorationsTree = new DecorationsTrees(); // Destroy my edit history and settings - this._commandManager = new EditStack(this); + this._commandManager.clear(); this._trimAutoWhitespaceLines = null; this._emitContentChangedEvent( @@ -483,6 +494,21 @@ export class TextModel extends Disposable implements model.ITextModel { ); } + _setEOL(eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { + try { + this._onDidChangeDecorations.beginDeferredEmit(); + this._eventEmitter.beginDeferredEmit(); + this._isUndoing = isUndoing; + this._isRedoing = isRedoing; + this.setEOL(eol); + this._overwriteAlternativeVersionId(resultingAlternativeVersionId); + } finally { + this._isUndoing = false; + this._eventEmitter.endDeferredEmit(resultingSelection); + this._onDidChangeDecorations.endDeferredEmit(); + } + } + private _onBeforeEOLChange(): void { // Ensure all decorations get their `range` set. const versionId = this.getVersionId(); @@ -1272,18 +1298,37 @@ export class TextModel extends Disposable implements model.ITextModel { return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } + _applyEdits(edits: model.IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] { + try { + this._onDidChangeDecorations.beginDeferredEmit(); + this._eventEmitter.beginDeferredEmit(); + this._isUndoing = isUndoing; + this._isRedoing = isRedoing; + let reverseEdits: model.IValidEditOperations[] = []; + for (let i = 0, len = edits.length; i < len; i++) { + reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) }; + } + this._overwriteAlternativeVersionId(resultingAlternativeVersionId); + return reverseEdits; + } finally { + this._isUndoing = false; + this._eventEmitter.endDeferredEmit(resultingSelection); + this._onDidChangeDecorations.endDeferredEmit(); + } + } + public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._applyEdits(this._validateEditOperations(rawOperations)); + return this._doApplyEdits(this._validateEditOperations(rawOperations)); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { + private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { const oldLineCount = this._buffer.getLineCount(); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); @@ -1364,62 +1409,20 @@ export class TextModel extends Disposable implements model.ITextModel { return result.reverseEdits; } - private _undo(): Selection[] | null { - this._isUndoing = true; - let r = this._commandManager.undo(); - this._isUndoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public undo(): Selection[] | null { - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._eventEmitter.beginDeferredEmit(); - return this._undo(); - } finally { - this._eventEmitter.endDeferredEmit(); - this._onDidChangeDecorations.endDeferredEmit(); - } + public undo(): void { + this._undoRedoService.undo(this.uri); } public canUndo(): boolean { - return this._commandManager.canUndo(); + return this._undoRedoService.canUndo(this.uri); } - private _redo(): Selection[] | null { - this._isRedoing = true; - let r = this._commandManager.redo(); - this._isRedoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public redo(): Selection[] | null { - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._eventEmitter.beginDeferredEmit(); - return this._redo(); - } finally { - this._eventEmitter.endDeferredEmit(); - this._onDidChangeDecorations.endDeferredEmit(); - } + public redo(): void { + this._undoRedoService.redo(this.uri); } public canRedo(): boolean { - return this._commandManager.canRedo(); + return this._undoRedoService.canRedo(this.uri); } //#endregion @@ -3191,10 +3194,11 @@ export class DidChangeContentEmitter extends Disposable { this._deferredCnt++; } - public endDeferredEmit(): void { + public endDeferredEmit(resultingSelection: Selection[] | null = null): void { this._deferredCnt--; if (this._deferredCnt === 0) { if (this._deferredEvent !== null) { + this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection; const e = this._deferredEvent; this._deferredEvent = null; this._fastEmitter.fire(e); diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index a09a1abf0fe..17f45db65ad 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IRange } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; /** * An event describing that the current mode associated with a model has changed. @@ -225,11 +226,14 @@ export class ModelRawContentChangedEvent { */ public readonly isRedoing: boolean; + public resultingSelection: Selection[] | null; + constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) { this.changes = changes; this.versionId = versionId; this.isUndoing = isUndoing; this.isRedoing = isRedoing; + this.resultingSelection = null; } public containsEvent(type: RawContentChangedType): boolean { diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index a46a64c4db5..20e32b9d2a7 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -25,6 +25,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; export interface IEditorSemanticHighlightingOptions { enabled?: boolean; @@ -103,6 +104,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { private readonly _configurationService: IConfigurationService; private readonly _configurationServiceSubscription: IDisposable; private readonly _resourcePropertiesService: ITextResourcePropertiesService; + private readonly _undoRedoService: IUndoRedoService; private readonly _onModelAdded: Emitter = this._register(new Emitter()); public readonly onModelAdded: Event = this._onModelAdded.event; @@ -126,11 +128,13 @@ export class ModelServiceImpl extends Disposable implements IModelService { @IConfigurationService configurationService: IConfigurationService, @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, @IThemeService themeService: IThemeService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IUndoRedoService undoRedoService: IUndoRedoService ) { super(); this._configurationService = configurationService; this._resourcePropertiesService = resourcePropertiesService; + this._undoRedoService = undoRedoService; this._models = {}; this._modelCreationOptionsByLanguageAndResource = Object.create(null); @@ -272,7 +276,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData { // create & save the model const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget); - const model: TextModel = new TextModel(value, options, languageIdentifier, resource); + const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService); const modelId = MODEL_ID(model.uri); if (this._models[modelId]) { diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index df936e17485..2e05ead3e79 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -15,6 +15,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; suite('FindModel', () => { @@ -44,7 +45,7 @@ suite('FindModel', () => { const factory = ptBuilder.finish(); withTestCodeEditor([], { - model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null) + model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null, new UndoRedoService()) }, (editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor) ); diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 64b26b23b76..6090bd19e7e 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -317,7 +317,7 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(model.getLineContent(1), 'one'); assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1)); - editor.trigger('keyboard', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Typing some text here on line one'); assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); }); @@ -447,7 +447,7 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(model.getLineContent(1), 'hello my dear world'); assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); - editor.trigger('keyboard', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'hello my dear'); assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); }); @@ -815,13 +815,13 @@ suite('Editor Contrib - Line Operations', () => { new Selection(2, 4, 2, 4) ]); - editor.trigger('tests', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.deepEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(1, 6, 1, 6), new Selection(3, 4, 3, 4) ]); - editor.trigger('tests', Handler.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.deepEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 249380f044a..4ebcba775cc 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -19,6 +19,7 @@ import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSe import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; class MockJSMode extends MockMode { @@ -47,7 +48,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService()); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService()); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 15ea81e6a44..2b1d99f2aa9 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -13,6 +13,7 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { Handler } from 'vs/editor/common/editorCommon'; import { Cursor } from 'vs/editor/common/controller/cursor'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; suite('WordOperations', () => { @@ -216,7 +217,7 @@ suite('WordOperations', () => { assert.equal(editor.getValue(), 'foo qbar baz'); - cursorCommand(cursor, Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(editor.getValue(), 'foo bar baz'); }); }); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index d3e251b6c54..1dc78d68e40 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -50,6 +50,8 @@ import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; export interface IEditorOverrideServices { [index: string]: any; @@ -150,7 +152,9 @@ export module StaticServices { export const logService = define(ILogService, () => new NullLogService()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o))); + export const undoRedoService = define(IUndoRedoService, () => new UndoRedoService()); + + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 6c6595c516e..fa60d5989ec 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -1240,22 +1240,22 @@ suite('Editor Controller - Regression tests', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert15'); }); @@ -1263,12 +1263,12 @@ suite('Editor Controller - Regression tests', () => { }); test('issue #23539: Setting model EOL isn\'t undoable', () => { - usingCursor({ - text: [ - 'Hello', - 'world' - ] - }, (model, cursor) => { + withTestCodeEditor([ + 'Hello', + 'world' + ], {}, (editor, cursor) => { + const model = editor.getModel()!; + assertCursor(cursor, new Position(1, 1)); model.setEOL(EndOfLineSequence.LF); assert.equal(model.getValue(), 'Hello\nworld'); @@ -1276,7 +1276,7 @@ suite('Editor Controller - Regression tests', () => { model.pushEOL(EndOfLineSequence.CRLF); assert.equal(model.getValue(), 'Hello\r\nworld'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), 'Hello\nworld'); }); }); @@ -1301,7 +1301,7 @@ suite('Editor Controller - Regression tests', () => { cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard'); assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2'); }); @@ -1327,39 +1327,39 @@ suite('Editor Controller - Regression tests', () => { assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world '); assertCursor(cursor, new Position(1, 13)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello'); assertCursor(cursor, new Position(1, 6)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), ''); assertCursor(cursor, new Position(1, 1)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello'); assertCursor(cursor, new Position(1, 6)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world '); assertCursor(cursor, new Position(1, 13)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); }); @@ -1735,21 +1735,21 @@ suite('Editor Controller - Regression tests', () => { '\t just some text' ].join('\n'), '001'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ ' some lines', ' and more lines', ' just some text', ].join('\n'), '002'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ 'some lines', 'and more lines', 'just some text', ].join('\n'), '003'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ 'some lines', 'and more lines', @@ -1935,10 +1935,8 @@ suite('Editor Controller - Regression tests', () => { }); test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => { - usingCursor({ - text: [ - ] - }, (model, cursor) => { + withTestCodeEditor([], {}, (editor, cursor) => { + const model = editor.getModel()!; assertCursor(cursor, new Position(1, 1)); // Typing sennsei in Japanese - Hiragana @@ -1957,7 +1955,7 @@ suite('Editor Controller - Regression tests', () => { assert.equal(model.getLineContent(1), 'せんせい'); assertCursor(cursor, new Position(1, 5)); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), ''); assertCursor(cursor, new Position(1, 1)); }); @@ -2138,7 +2136,7 @@ suite('Editor Controller - Regression tests', () => { }], () => [new Selection(1, 1, 1, 1)]); assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); }); @@ -2229,12 +2227,12 @@ suite('Editor Controller - Regression tests', () => { new Selection(1, 5, 1, 5), ]); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 4, 1, 4), ]); - cursorCommand(cursor, H.Redo, null, 'keyboard'); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 5, 1, 5), ]); @@ -2263,7 +2261,7 @@ suite('Editor Controller - Regression tests', () => { new Selection(1, 1, 1, 1), ]); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 1, 1, 1), ]); @@ -2378,49 +2376,49 @@ suite('Editor Controller - Cursor Configuration', () => { CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 2 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'M y Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 3 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 4 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My S econd Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My S econd Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 13 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Li ne123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 14 assert.equal(model.getLineContent(2), 'My Second Line123'); @@ -2774,7 +2772,7 @@ suite('Editor Controller - Cursor Configuration', () => { assert.equal(model.getLineContent(2), 'a '); // Undo DeleteLeft - get us back to original indentation - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' a '); // Nothing is broken when cursor is in (1,1) @@ -2859,22 +2857,22 @@ suite('Editor Controller - Cursor Configuration', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16'); }); @@ -2895,7 +2893,7 @@ suite('Editor Controller - Cursor Configuration', () => { const beforeVersion = model.getVersionId(); const beforeAltVersion = model.getAlternativeVersionId(); cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); const afterVersion = model.getVersionId(); const afterAltVersion = model.getAlternativeVersionId(); @@ -4263,7 +4261,7 @@ suite('autoClosingPairs', () => { moveTo(cursor, lineNumber, column); cursorCommand(cursor, H.Type, { text: chr }, 'keyboard'); assert.deepEqual(model.getLineContent(lineNumber), expected, message); - cursorCommand(cursor, H.Undo); + model.undo(); } test('open parens: default', () => { @@ -5347,11 +5345,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A fir line'); assertCursor(cursor, new Selection(1, 6, 1, 6)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); @@ -5376,11 +5374,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A firstine'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); @@ -5410,11 +5408,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'Second line'); assertCursor(cursor, new Selection(2, 7, 2, 7)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' line'); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 8, 2, 8)); }); @@ -5448,11 +5446,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), ''); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' line'); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 8, 2, 8)); }); @@ -5479,11 +5477,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'Another text'); assertCursor(cursor, new Selection(2, 13, 2, 13)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another '); assertCursor(cursor, new Selection(2, 9, 2, 9)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 9, 2, 9)); }); @@ -5515,11 +5513,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'An'); assertCursor(cursor, new Selection(2, 3, 2, 3)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another '); assertCursor(cursor, new Selection(2, 9, 2, 9)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 9, 2, 9)); }); @@ -5539,15 +5537,15 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A first and interesting line'); assertCursor(cursor, new Selection(1, 24, 1, 24)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first and line'); assertCursor(cursor, new Selection(1, 12, 1, 12)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 47a341a2d63..d138d2e396a 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -84,6 +84,7 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test } let editor = createTestCodeEditor(options); + editor.getCursor()!.setHasFocus(true); callback(editor, editor.getCursor()!); editor.dispose(); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index b6d04d367a2..cd2df2cb74e 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -18,6 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; const GENERATE_TESTS = false; @@ -29,7 +30,7 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); }); teardown(() => { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index c30c37e1394..ffef4c6d2d4 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -11,8 +11,9 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { UriDto } from 'vs/base/common/types'; export interface ILocalizedString { value: string; @@ -28,9 +29,7 @@ export interface ICommandAction { toggled?: ContextKeyExpr; } -type Serialized = { [K in keyof T]: T[K] extends URI ? UriComponents : Serialized }; - -export type ISerializableCommandAction = Serialized; +export type ISerializableCommandAction = UriDto; export interface IMenuItem { command: ICommandAction; diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index abcae819e57..05975998d51 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -113,6 +113,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema { scope?: ConfigurationScope; included?: boolean; tags?: string[]; + disallowSyncIgnore?: boolean; } export interface IConfigurationExtensionInfo { diff --git a/src/vs/platform/undoRedo/common/undoRedo.ts b/src/vs/platform/undoRedo/common/undoRedo.ts index 711c2058548..75e08d09dcc 100644 --- a/src/vs/platform/undoRedo/common/undoRedo.ts +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -16,7 +16,7 @@ export interface IUndoRedoElement { /** * None, one or multiple resources that this undo/redo element impacts. */ - readonly resources: URI[]; + readonly resources: readonly URI[]; /** * The label of the undo/redo element. @@ -43,7 +43,7 @@ export interface IUndoRedoElement { * Invalidate the edits concerning `resource`. * i.e. the undo/redo stack for that particular resource has been destroyed. */ - invalidate(resource: URI): boolean; + invalidate(resource: URI): void; } export interface IUndoRedoService { diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 60c98764c17..dfa7fb4cea2 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -7,11 +7,12 @@ import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/ import { URI } from 'vs/base/common/uri'; import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; class StackElement { public readonly actual: IUndoRedoElement; public readonly label: string; - public readonly resources: URI[]; + public readonly resources: readonly URI[]; public readonly strResources: string[]; constructor(actual: IUndoRedoElement) { @@ -179,7 +180,7 @@ export class UndoRedoService implements IUndoRedoService { return false; } - redo(resource: URI): void { + public redo(resource: URI): void { const strResource = uriGetComparisonKey(resource); if (!this._editStacks.has(strResource)) { return; @@ -239,3 +240,5 @@ export class UndoRedoService implements IUndoRedoService { } } } + +registerSingleton(IUndoRedoService, UndoRedoService); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 8794565aa5a..cdf2baeef81 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -198,8 +198,9 @@ export abstract class AbstractSynchroniser extends Disposable { private async cleanUpBackup(): Promise { const stat = await this.fileService.resolve(this.syncFolder); if (stat.children) { - const toDelete = stat.children.filter(stat => { - if (stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)); + if (all.length > 10) { + const toDelete = all.filter(stat => { const ctime = stat.ctime || new Date( parseInt(stat.name.substring(0, 4)), parseInt(stat.name.substring(4, 6)) - 1, @@ -209,13 +210,12 @@ export abstract class AbstractSynchroniser extends Disposable { parseInt(stat.name.substring(13, 15)) ).getTime(); return Date.now() - ctime > BACK_UP_MAX_AGE; - } - return false; - }); - await Promise.all(toDelete.map(stat => { - this.logService.info('Deleting from backup', stat.resource.path); - this.fileService.del(stat.resource); - })); + }); + await Promise.all(toDelete.map(stat => { + this.logService.info('Deleting from backup', stat.resource.path); + this.fileService.del(stat.resource); + })); + } } } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 51e597a8add..0c189d343c9 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -78,8 +78,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remoteUserData.syncData !== null) { const localExtensions = await this.getLocalExtensions(); const remoteExtensions = this.parseExtensions(remoteUserData.syncData); - const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions()); - await this.apply({ added, removed: [], updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }); + const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions()); + await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }); } // No remote exists to pull diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 3c6191259ee..abb2996f999 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -260,9 +260,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (content !== null) { - if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); - } + this.validateContent(content); if (hasLocalChanged) { this.logService.trace('Settings: Updating local settings...'); @@ -317,21 +315,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; - - // No action when there are errors - if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); - } - - else { - this.logService.trace('Settings: Merging remote settings with local settings...'); - const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); - content = result.localContent || result.remoteContent; - hasLocalChanged = result.localContent !== null; - hasRemoteChanged = result.remoteContent !== null; - hasConflicts = result.hasConflicts; - conflictSettings = result.conflictsSettings; - } + this.validateContent(localContent); + this.logService.trace('Settings: Merging remote settings with local settings...'); + const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); + content = result.localContent || result.remoteContent; + hasLocalChanged = result.localContent !== null; + hasRemoteChanged = result.remoteContent !== null; + hasConflicts = result.hasConflicts; + conflictSettings = result.conflictsSettings; } // First time syncing to remote @@ -364,4 +355,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } return null; } + + private validateContent(content: string): void { + if (this.hasErrors(content)) { + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + } + } } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 4cd08868110..b64192c62d6 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -6,7 +6,7 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncSource, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { @@ -17,8 +17,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private successiveFailures: number = 0; private readonly syncDelayer: Delayer; - private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>()); - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; + private readonly _onError: Emitter = this._register(new Emitter()); + readonly onError: Event = this._onError.event; constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -62,27 +62,23 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto await this.userDataSyncService.sync(); this.resetFailures(); } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.TurnedOff) { + const error = UserDataSyncError.toUserDataSyncError(e); + if (error.code === UserDataSyncErrorCode.TurnedOff || error.code === UserDataSyncErrorCode.SessionExpired) { this.logService.info('Auto Sync: Sync is turned off in the cloud.'); this.logService.info('Auto Sync: Resetting the local sync state.'); await this.userDataSyncService.resetLocal(); this.logService.info('Auto Sync: Completed resetting the local sync state.'); if (auto) { - return this.userDataSyncEnablementService.setEnablement(false); + this.userDataSyncEnablementService.setEnablement(false); + this._onError.fire(error); + return; } else { return this.sync(loop, auto); } } - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) { - this.logService.info('Auto Sync: Cloud has new session'); - this.logService.info('Auto Sync: Resetting the local sync state.'); - await this.userDataSyncService.resetLocal(); - this.logService.info('Auto Sync: Completed resetting the local sync state.'); - return this.sync(loop, auto); - } - this.logService.error(e); + this.logService.error(error); this.successiveFailures++; - this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + this._onError.fire(error); } if (loop) { await timeout(1000 * 60 * 5); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b6e0b1120c7..240d6ff1334 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -38,6 +38,7 @@ export interface ISyncConfiguration { export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; + const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ id: 'sync', @@ -84,14 +85,11 @@ export function registerConfiguration(): IDisposable { 'sync.ignoredExtensions': { 'type': 'array', 'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."), - items: { - type: 'string', - pattern: EXTENSION_IDENTIFIER_PATTERN, - errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") - }, + $ref: ignoredExtensionsSchemaId, 'default': [], 'scope': ConfigurationScope.APPLICATION, - uniqueItems: true + uniqueItems: true, + disallowSyncIgnore: true }, 'sync.ignoredSettings': { 'type': 'array', @@ -100,12 +98,13 @@ export function registerConfiguration(): IDisposable { 'scope': ConfigurationScope.APPLICATION, $ref: ignoredSettingsSchemaId, additionalProperties: true, - uniqueItems: true + uniqueItems: true, + disallowSyncIgnore: true } } }); + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const registerIgnoredSettingsSchema = () => { - const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const ignoredSettingsSchema: IJSONSchema = { items: { type: 'string', @@ -114,6 +113,11 @@ export function registerConfiguration(): IDisposable { }; jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); }; + jsonRegistry.registerSchema(ignoredExtensionsSchemaId, { + type: 'string', + pattern: EXTENSION_IDENTIFIER_PATTERN, + errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") + }); return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema()); } @@ -300,7 +304,7 @@ export interface IUserDataSyncService { export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; + readonly onError: Event; triggerAutoSync(): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 755312ee7bd..94e476f67f2 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -82,7 +82,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } - this.updateLastSyncTime(Date.now()); + this.updateLastSyncTime(); } async push(): Promise { @@ -94,7 +94,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } - this.updateLastSyncTime(Date.now()); + this.updateLastSyncTime(); } async sync(): Promise { @@ -140,7 +140,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); - this.updateLastSyncTime(Date.now()); + this.updateLastSyncTime(); } finally { this.updateStatus(); @@ -166,7 +166,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async accept(source: SyncSource, content: string): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(source); - return synchroniser.accept(content); + await synchroniser.accept(content); } async getRemoteContent(source: SyncSource, preview: boolean): Promise { @@ -238,9 +238,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private setStatus(status: SyncStatus): void { + const oldStatus = this._status; if (this._status !== status) { this._status = status; this._onDidChangeStatus.fire(status); + if (oldStatus === SyncStatus.HasConflicts) { + this.updateLastSyncTime(); + } } } @@ -267,11 +271,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return SyncStatus.Idle; } - private updateLastSyncTime(lastSyncTime: number): void { - if (this._lastSyncTime !== lastSyncTime) { - this._lastSyncTime = lastSyncTime; - this.storageService.store(LAST_SYNC_TIME_KEY, lastSyncTime, StorageScope.GLOBAL); - this._onDidChangeLastSyncTime.fire(lastSyncTime); + private updateLastSyncTime(): void { + if (this.status === SyncStatus.Idle) { + this._lastSyncTime = new Date().getTime(); + this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL); + this._onDidChangeLastSyncTime.fire(this._lastSyncTime); } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index f51e0aaedfb..8851128af0e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -19,7 +19,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 0edf9036dc1..455185f6f7d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -114,7 +114,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index febe7f0ec0d..877dceec762 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,6 +52,8 @@ import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { Dto } from 'vs/base/common/types'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -1235,16 +1237,7 @@ export interface ICodeLensDto { command?: ICommandDto; } -export interface ICallHierarchyItemDto { - _sessionId: string; - _itemId: string; - kind: modes.SymbolKind; - name: string; - detail?: string; - uri: UriComponents; - range: IRange; - selectionRange: IRange; -} +export type ICallHierarchyItemDto = Dto; export interface IIncomingCallDto { from: ICallHierarchyItemDto; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 338232d2a77..4eed027e83e 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -19,6 +19,7 @@ import { Cache } from './cache'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; type IconPath = URI | { light: URI, dark: URI }; @@ -37,7 +38,7 @@ export class ExtHostWebview implements vscode.Webview { private readonly _initData: WebviewInitData, private readonly _workspace: IExtHostWorkspace | undefined, private readonly _extension: IExtensionDescription, - private readonly _logService: ILogService, + private readonly _deprecationService: IExtHostApiDeprecationService, ) { } public dispose() { @@ -63,11 +64,10 @@ export class ExtHostWebview implements vscode.Webview { this.assertNotDisposed(); if (this._html !== value) { this._html = value; - if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { - if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { - this._hasCalledAsWebviewUri = true; - this._logService.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); - } + if (!this._hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { + this._hasCalledAsWebviewUri = true; + this._deprecationService.report('Webview vscode-resource: uris', this._extension, + `Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } this._proxy.$setHtml(this._handle, value); } @@ -271,6 +271,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly initData: WebviewInitData, private readonly workspace: IExtHostWorkspace | undefined, private readonly _logService: ILogService, + private readonly _deprecationService: IExtHostApiDeprecationService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -291,7 +292,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const handle = ExtHostWebviews.newHandle(); this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; @@ -414,7 +415,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); await serializer.deserializeWebviewPanel(revivedPanel, state); @@ -434,7 +435,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); const revivedResource = URI.revive(resource); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 53b4a803ee9..943d5ad7988 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1180,6 +1180,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { this.setEditorHidden(false); this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height }); + this.editorGroupService.activeGroup.focus(); } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 440e05be22f..3d4807ced1d 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -21,9 +21,9 @@ import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; export interface ICompositeBarItem { id: string; @@ -33,28 +33,11 @@ export interface ICompositeBarItem { visible: boolean; } -export class CompositeDragAndDropData implements IDragAndDropData { - constructor( - private type: 'view' | 'composite', - private id: string - ) { } - update(dataTransfer: DataTransfer): void { - // no-op - } - getData(): { type: 'view' | 'composite', id: string } { - return { type: this.type, id: this.id }; - } -} - -export interface ICompositeDragAndDrop { - drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void; -} - export class CompositeDragAndDrop implements ICompositeDragAndDrop { constructor( private viewDescriptorService: IViewDescriptorService, - private viewContainerLocation: ViewContainerLocation, + private targetContainerLocation: ViewContainerLocation, private openComposite: (id: string, focus?: boolean) => void, private moveComposite: (from: string, to: string) => void, private getVisibleCompositeIds: () => string[] @@ -67,7 +50,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { const currentContainer = viewContainerRegistry.get(dragData.id)!; const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer); if (targetCompositeId) { - if (currentLocation !== this.viewContainerLocation && this.viewContainerLocation !== ViewContainerLocation.Panel) { + if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); if (destinationContainer) { this.viewDescriptorService.moveViewsToContainer(this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView), destinationContainer); @@ -83,16 +66,16 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (targetCompositeId) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); if (destinationContainer) { - if (this.viewContainerLocation === ViewContainerLocation.Sidebar) { + if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer); this.openComposite(targetCompositeId, true); } else { - this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.viewContainerLocation); + this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation); this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId); } } } else { - this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.viewContainerLocation); + this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation); const newCompositeId = this.viewDescriptorService.getViewContainer(dragData.id)!.id; const visibleItems = this.getVisibleCompositeIds(); const targetId = visibleItems.length ? visibleItems[visibleItems.length - 1] : undefined; @@ -106,6 +89,55 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } } + onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean { + const dragData = data.getData(); + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + if (dragData.type === 'composite') { + // Dragging a composite + const currentContainer = viewContainerRegistry.get(dragData.id)!; + const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer); + + // ... to the same location + if (currentLocation === this.targetContainerLocation) { + return true; + } + + // ... across view containers but without a destination composite + if (!targetCompositeId) { + return false; + } + + // ... from panel to the sidebar + if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { + const destinationContainer = viewContainerRegistry.get(targetCompositeId); + return !!destinationContainer && + this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView); + } + // ... from sidebar to the panel + else { + return false; + } + } else { + // Dragging an individual view + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id); + + // ... that cannot move + if (!viewDescriptor || !viewDescriptor.canMoveView) { + return false; + } + + // ... to create a view container + if (!targetCompositeId) { + return this.targetContainerLocation === ViewContainerLocation.Panel; + } + + // ... into a destination + return true; + } + + return false; + } } export interface ICompositeBarOptions { @@ -227,6 +259,36 @@ export class CompositeBar extends Widget implements ICompositeBar { } })); + this._register(addDisposableListener(parent, EventType.DRAG_OVER, (e: DragEvent) => { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + EventHelper.stop(e, true); + + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + + // Check if drop is allowed + if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) { + e.dataTransfer.dropEffect = 'none'; + } + } + } + + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + EventHelper.stop(e, true); + + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data)) { + const draggedViewId = data[0].id; + + // Check if drop is allowed + if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) { + e.dataTransfer.dropEffect = 'none'; + } + } + } + })); + return actionBarDiv; } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index bc73260d6c8..f09ef9d3c3a 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -20,8 +20,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter } from 'vs/base/common/event'; import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; -import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/workbench/browser/parts/compositeBar'; import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; export interface ICompositeActivity { badge: IBadge; @@ -549,6 +549,31 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } }, + onDragOver: e => { + dom.EventHelper.stop(e, true); + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + if (draggedCompositeId !== this.activity.id) { + if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e)) { + e.dataTransfer.dropEffect = 'none'; + } + } + } + } + + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data)) { + const draggedViewId = data[0].id; + if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e)) { + e.dataTransfer.dropEffect = 'none'; + } + } + } + }, + onDragLeave: e => { if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { this.updateFromDragging(container, false); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 22b2356893c..1d49f9aed7a 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -16,6 +16,9 @@ import { Event } from 'vs/base/common/event'; import { isEmptyObject } from 'vs/base/common/types'; import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { MementoObject } from 'vs/workbench/common/memento'; +import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { isLinux } from 'vs/base/common/platform'; +import { indexOfPath } from 'vs/base/common/extpath'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -249,6 +252,34 @@ export class EditorMemento implements IEditorMemento { } } + moveEditorState(source: URI, target: URI): void { + const cache = this.doLoad(); + + const cacheKeys = cache.keys(); + for (const cacheKey of cacheKeys) { + const resource = URI.parse(cacheKey); + + if (!isEqualOrParent(resource, source)) { + continue; // not matching our resource + } + + // Determine new resulting target resource + let targetResource: URI; + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved + } else { + const index = indexOfPath(resource.path, source.path, !isLinux); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved + } + + const value = cache.get(cacheKey); + if (value) { + cache.delete(cacheKey); + cache.set(targetResource.toString(), value); + } + } + } + private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined { if (resourceOrEditor instanceof EditorInput) { return resourceOrEditor.resource; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 96f29026794..bb60338a50c 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -204,9 +204,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this.editorControl; } - /** - * Saves the text editor view state for the given resource. - */ protected saveTextEditorViewState(resource: URI): void { const editorViewState = this.retrieveTextEditorViewState(resource); if (!editorViewState || !this.group) { @@ -248,22 +245,20 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return control.saveViewState(); } - /** - * Clears the text editor view state for the given resources. - */ + protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { + return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; + } + + protected moveTextEditorViewState(source: URI, target: URI): void { + return this.editorMemento.moveEditorState(source, target); + } + protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void { resources.forEach(resource => { this.editorMemento.clearEditorState(resource, group); }); } - /** - * Loads the text editor view state for the given resource and returns it. - */ - protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { - return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; - } - private updateEditorConfiguration(configuration?: IEditorConfiguration): void { if (!configuration) { const resource = this.getActiveResource(); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 27a5cc41671..ec2e7f8df87 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -980,7 +980,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } if (!this.areExtensionsReady) { if (this.visibleViewsCountFromCache === undefined) { - return false; + return true; } // Check in cache so that view do not jump. See #29609 return this.visibleViewsCountFromCache === 1; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 6de4ce3b133..23c66ef5033 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1391,6 +1391,8 @@ export interface IEditorMemento { clearEditorState(resource: URI, group?: IEditorGroup): void; clearEditorState(editor: EditorInput, group?: IEditorGroup): void; + + moveEditorState(source: URI, target: URI): void; } class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 55b2be2978c..6ffd11e782c 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 32450e7f538..e26952f121b 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/callHierarchy'; import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 2e9cec145d3..d01a50c4f9e 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; -import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts similarity index 100% rename from src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts rename to src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 265c9164ea4..8f0b1969062 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -442,7 +442,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, - title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Don't Sync This Extension"), original: `Don't Sync This Extension` }, + title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), original: `Sync This Extension` }, menu: { id: MenuId.ExtensionContext, group: '2_configure', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8b70db39953..dec8ffa2385 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -766,7 +766,7 @@ export class MenuItemExtensionAction extends ExtensionAction { return; } if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { - this.checked = this.configurationService.getValue('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier)); + this.checked = !this.configurationService.getValue('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier)); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index e47a31e898d..24ac77635c9 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -16,7 +16,7 @@ import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbenc import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -61,16 +61,25 @@ export class TextFileEditor extends BaseTextEditor { this.updateRestoreViewStateConfiguration(); // Clear view state for deleted files - this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Move view state for moved files + this._register(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); } - private onFilesChanged(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { const deleted = e.getDeleted(); if (deleted?.length) { this.clearTextEditorViewState(deleted.map(d => d.resource)); } } + private onDidRunOperation(e: FileOperationEvent): void { + if (e.operation === FileOperation.MOVE && e.target) { + this.moveTextEditorViewState(e.resource, e.target.resource); + } + } + protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void { super.handleConfigurationChangeEvent(configuration); diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 313f63a7d16..595966849a2 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput, GroupIdentifier, IMoveResult } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput, GroupIdentifier, IMoveResult, isTextEditor } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -19,6 +19,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isEqual } from 'vs/base/common/resources'; import { Event } from 'vs/base/common/event'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; const enum ForceOpenAs { None, @@ -280,11 +281,26 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi return { editor: { resource: target, - encoding: this.getEncoding() + encoding: this.getEncoding(), + options: { + viewState: this.getViewStateFor(group) + } } }; } + private getViewStateFor(group: GroupIdentifier): IEditorViewState | undefined { + for (const editor of this.editorService.visibleControls) { + if (editor.group.id === group && isEqual(editor.input.resource, this.resource)) { + if (isTextEditor(editor)) { + return editor.getViewState(); + } + } + } + + return undefined; + } + matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index cefd280206f..ea753245ff4 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -112,7 +112,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } const outputView = await this.viewsService.openView(OUTPUT_VIEW_ID, !preserveFocus); if (outputView && channel) { - outputView!.showChannel(channel, !!preserveFocus); + outputView.showChannel(channel, !!preserveFocus); } } diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 4f455e49071..f5cc76f8b4d 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -130,8 +130,11 @@ export class OutputViewPane extends ViewPane { private onDidChangeVisibility(visible: boolean): void { this.editor.setVisible(visible); - const channel = this.channelId ? this.outputService.getChannel(this.channelId) : undefined; - if (visible && channel) { + let channel: IOutputChannel | undefined = undefined; + if (visible) { + channel = this.channelId ? this.outputService.getChannel(this.channelId) : this.outputService.getActiveChannel(); + } + if (channel) { this.setInput(channel); } else { this.clearInput(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 823554a88dd..8d1f6e2ab07 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1237,7 +1237,7 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting): IAction[] { const enableSync = this._userDataSyncEnablementService.isEnabled(); - return enableSync ? + return enableSync && !setting.disallowSyncIgnore ? [this._instantiationService.createInstance(StopSyncingSettingAction, setting)] : []; } @@ -1624,7 +1624,7 @@ class CopySettingAsJSONAction extends Action { class StopSyncingSettingAction extends Action { static readonly ID = 'settings.stopSyncingSetting'; - static readonly LABEL = localize('stopSyncingSetting', "Don't Sync This Setting"); + static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting"); constructor( private readonly setting: ISetting, @@ -1636,15 +1636,15 @@ class StopSyncingSettingAction extends Action { update() { const ignoredSettings = getIgnoredSettings(this.configService); - this.checked = ignoredSettings.includes(this.setting.key); + this.checked = !ignoredSettings.includes(this.setting.key); } async run(): Promise { let currentValue = [...this.configService.getValue('sync.ignoredSettings')]; if (this.checked) { - currentValue = currentValue.filter(v => v !== this.setting.key); - } else { currentValue.push(this.setting.key); + } else { + currentValue = currentValue.filter(v => v !== this.setting.key); } this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 0da45c12686..c015e1665f5 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -10,15 +10,12 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IInputValidator, HistoryInputBox, IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { Delayer } from 'vs/base/common/async'; import type { IThemable } from 'vs/base/common/styler'; export interface IOptions { @@ -50,11 +47,8 @@ export class PatternInputWidget extends Widget implements IThemable { private _onCancel = this._register(new Emitter()); onCancel: CommonEvent = this._onCancel.event; - private searchOnTypeDelayer: Delayer; - constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService protected themeService: IThemeService, - @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -62,8 +56,6 @@ export class PatternInputWidget extends Widget implements IThemable { this.placeholder = options.placeholder || ''; this.ariaLabel = options.ariaLabel || nls.localize('defaultLabel', "input"); - this._register(this.searchOnTypeDelayer = new Delayer(this.searchConfig.searchOnTypeDebouncePeriod)); - this.render(options); parent.appendChild(this.domNode); @@ -152,6 +144,8 @@ export class PatternInputWidget extends Widget implements IThemable { history: options.history || [] }, this.contextKeyService); this._register(attachInputBoxStyler(this.inputBox, this.themeService)); + this._register(this.inputBox.onDidChange(() => this._onSubmit.fire(true))); + this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); @@ -170,24 +164,13 @@ export class PatternInputWidget extends Widget implements IThemable { switch (keyboardEvent.keyCode) { case KeyCode.Enter: this.onSearchSubmit(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), 0); + this._onSubmit.fire(false); return; case KeyCode.Escape: this._onCancel.fire(); return; - case KeyCode.Tab: case KeyCode.Tab | KeyMod.Shift: return; - default: - if (this.searchConfig.searchOnType) { - this._onCancel.fire(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); - } - return; } } - - private get searchConfig() { - return this.configurationService.getValue('search'); - } } export class ExcludePatternInputWidget extends PatternInputWidget { @@ -197,10 +180,9 @@ export class ExcludePatternInputWidget extends PatternInputWidget { constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(parent, contextViewProvider, options, themeService, configurationService, contextKeyService); + super(parent, contextViewProvider, options, themeService, contextKeyService); } private useExcludesAndIgnoreFilesBox!: Checkbox; diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 0f2b65453f5..0b16e7a65ad 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -283,7 +283,7 @@ export class RefreshAction extends Action { run(): Promise { const searchView = getSearchView(this.viewsService); if (searchView) { - searchView.onQueryChanged(false); + searchView.triggerQueryChange({ preserveFocus: false }); } return Promise.resolve(); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index c2fdc1c21fc..94530fb3543 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -145,6 +145,9 @@ export class SearchView extends ViewPane { private toggleCollapseStateDelayer: Delayer; + private triggerQueryDelayer: Delayer; + private pauseSearching = false; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -221,6 +224,7 @@ export class SearchView extends ViewPane { this.addToSearchHistoryDelayer = this._register(new Delayer(2000)); this.toggleCollapseStateDelayer = this._register(new Delayer(100)); + this.triggerQueryDelayer = this._register(new Delayer(0)); const collapseDeepestExpandedLevelAction = this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL); const expandAllAction = this.instantiationService.createInstance(ExpandAllAction, ExpandAllAction.ID, ExpandAllAction.LABEL); @@ -315,7 +319,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes.setValue(patternIncludes); - this.inputPatternIncludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)); this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); @@ -331,9 +335,9 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(patternExclusions); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles); - this.inputPatternExcludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); this.inputPatternExcludes.onCancel(() => this.cancelSearch(false)); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.onQueryChanged(true)); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerQueryChange()); this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); this.messagesElement = dom.append(this.container, $('.messages')); @@ -436,9 +440,9 @@ export class SearchView extends ViewPane { this.searchWidget.toggleReplace(true); } - this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType))); + this._register(this.searchWidget.onSearchSubmit(options => this.triggerQueryChange(options))); this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); - this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true))); + this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.triggerQueryChange())); this._register(this.searchWidget.onDidHeightChange(() => this.reLayout())); @@ -869,9 +873,11 @@ export class SearchView extends ViewPane { if (this.searchWidget.searchInput.getRegex()) { selectedText = strings.escapeRegExpCharacters(selectedText); } - this.searchWidget.setValue(selectedText, true); + this.pauseSearching = true; + this.searchWidget.setValue(selectedText); + this.pauseSearching = false; updatedText = true; - if (this.searchConfig.searchOnType) { this.onQueryChanged(false); } + if (this.searchConfig.searchOnType) { this.triggerQueryChange(); } } } @@ -1099,17 +1105,17 @@ export class SearchView extends ViewPane { toggleCaseSensitive(): void { this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive()); - this.onQueryChanged(true); + this.triggerQueryChange(); } toggleWholeWords(): void { this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords()); - this.onQueryChanged(true); + this.triggerQueryChange(); } toggleRegex(): void { this.searchWidget.searchInput.setRegex(!this.searchWidget.searchInput.getRegex()); - this.onQueryChanged(true); + this.triggerQueryChange(); } setSearchParameters(args: IFindInFilesArgs = {}): void { @@ -1139,7 +1145,7 @@ export class SearchView extends ViewPane { } } if (typeof args.triggerSearch === 'boolean' && args.triggerSearch) { - this.onQueryChanged(true); + this.triggerQueryChange(); } } @@ -1228,7 +1234,17 @@ export class SearchView extends ViewPane { this.searchWidget.focus(false); } - onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { + triggerQueryChange(_options?: { preserveFocus?: boolean, triggeredOnType?: boolean, delay?: number }) { + const options = { preserveFocus: true, triggeredOnType: false, delay: 0, ..._options }; + + if (!this.pauseSearching) { + this.triggerQueryDelayer.trigger(() => { + this._onQueryChanged(options.preserveFocus, options.triggeredOnType); + }, options.delay); + } + } + + private _onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { if (!this.searchWidget.searchInput.inputBox.isInputValid()) { return; } @@ -1409,7 +1425,7 @@ export class SearchView extends ViewPane { const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again"))); this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - this.onQueryChanged(false); + this.triggerQueryChange({ preserveFocus: false }); })); } else if (hasIncludes || hasExcludes) { const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files"))); @@ -1419,7 +1435,7 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(''); this.inputPatternIncludes.setValue(''); - this.onQueryChanged(false); + this.triggerQueryChange({ preserveFocus: false }); })); } else { const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings"))); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 3f8d572d78a..863e6cbe446 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -121,12 +121,11 @@ export class SearchWidget extends Widget { private replaceActive: IContextKey; private replaceActionBar!: ActionBar; private _replaceHistoryDelayer: Delayer; - private _searchDelayer: Delayer; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string | null = null; - private _onSearchSubmit = this._register(new Emitter()); - readonly onSearchSubmit: Event = this._onSearchSubmit.event; + private _onSearchSubmit = this._register(new Emitter<{ triggeredOnType: boolean, delay: number }>()); + readonly onSearchSubmit: Event<{ triggeredOnType: boolean, delay: number }> = this._onSearchSubmit.event; private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>()); readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event; @@ -177,7 +176,6 @@ export class SearchWidget extends Widget { this._replaceHistoryDelayer = new Delayer(500); - this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); this.configurationService.onDidChangeConfiguration(e => { @@ -447,10 +445,8 @@ export class SearchWidget extends Widget { this._onReplaceToggled.fire(); } - setValue(value: string, skipSearchOnChange: boolean) { - this.temporarilySkipSearchOnChange = skipSearchOnChange; + setValue(value: string) { this.searchInput.setValue(value); - this.temporarilySkipSearchOnChange = false; } setReplaceAllActionState(enabled: boolean): void { @@ -512,12 +508,12 @@ export class SearchWidget extends Widget { matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` 10; // only things matching empty string - this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); } catch { // pass } } else { - this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod); } } } @@ -628,7 +624,7 @@ export class SearchWidget extends Widget { } } - private submitSearch(triggeredOnType = false): void { + private submitSearch(triggeredOnType = false, delay: number = 0): void { this.searchInput.validate(); if (!this.searchInput.inputBox.isInputValid()) { return; @@ -639,7 +635,7 @@ export class SearchWidget extends Widget { if (value && useGlobalFindBuffer) { this.clipboardServce.writeFindText(value); } - this._onSearchSubmit.fire(triggeredOnType); + this._onSearchSubmit.fire({ triggeredOnType, delay }); } contextLines() { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 4f972472eca..66cdb94e6e0 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -68,7 +68,7 @@ export class SearchEditor extends BaseTextEditor { private toggleQueryDetailsButton!: HTMLElement; private messageBox!: HTMLElement; - private runSearchDelayer = new Delayer(300); + private runSearchDelayer = new Delayer(0); private pauseSearching: boolean = false; private showingIncludesExcludes: boolean = false; private inSearchEditorContextKey: IContextKey; @@ -121,9 +121,9 @@ export class SearchEditor extends BaseTextEditor { this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true })); this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); - this.queryEditorWidget.onSearchSubmit(() => this.runSearch(true, true)); // onSearchSubmit has an internal delayer, so skip over ours. - this.queryEditorWidget.searchInput.onDidOptionChange(() => this.runSearch(false)); - this.queryEditorWidget.onDidToggleContext(() => this.runSearch(false)); + this.queryEditorWidget.onSearchSubmit(({ delay }) => this.triggerSearch({ delay })); + this.queryEditorWidget.searchInput.onDidOptionChange(() => this.triggerSearch({ resetCursor: false })); + this.queryEditorWidget.onDidToggleContext(() => this.triggerSearch({ resetCursor: false })); // Includes/Excludes Dropdown this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes')); @@ -161,7 +161,7 @@ export class SearchEditor extends BaseTextEditor { this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: localize('label.includes', 'Search Include Patterns'), })); - this.inputPatternIncludes.onSubmit(_triggeredOnType => this.runSearch()); + this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); // // Excludes const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes')); @@ -170,8 +170,8 @@ export class SearchEditor extends BaseTextEditor { this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), })); - this.inputPatternExcludes.onSubmit(_triggeredOnType => this.runSearch()); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.runSearch()); + this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch()); [this.queryEditorWidget.searchInput, this.inputPatternIncludes, this.inputPatternExcludes].map(input => this._register(attachInputBoxStyler(input, this.themeService, { inputBorder: searchEditorTextInputBorder }))); @@ -188,7 +188,7 @@ export class SearchEditor extends BaseTextEditor { if (show) { const runAgainLink = DOM.append(this.messageBox, DOM.$('a.pointer.prominent.message', {}, localize('runSearch', "Run Search"))); this.messageDisposables.push(DOM.addDisposableListener(runAgainLink, DOM.EventType.CLICK, async () => { - await this.runSearch(true, true); + await this.triggerSearch(); this.toggleRunAgainMessage(false); })); } @@ -271,17 +271,17 @@ export class SearchEditor extends BaseTextEditor { toggleWholeWords() { this.queryEditorWidget.searchInput.setWholeWords(!this.queryEditorWidget.searchInput.getWholeWords()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleRegex() { this.queryEditorWidget.searchInput.setRegex(!this.queryEditorWidget.searchInput.getRegex()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleCaseSensitive() { this.queryEditorWidget.searchInput.setCaseSensitive(!this.queryEditorWidget.searchInput.getCaseSensitive()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleContextLines() { @@ -292,16 +292,22 @@ export class SearchEditor extends BaseTextEditor { this.toggleIncludesExcludes(); } - async runSearch(resetCursor = true, instant = false) { + private get searchConfig(): ISearchConfigurationProperties { + return this.configurationService.getValue('search'); + } + + async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) { + const options = { resetCursor: true, delay: 0, ..._options }; + if (!this.pauseSearching) { await this.runSearchDelayer.trigger(async () => { await this.doRunSearch(); this.toggleRunAgainMessage(false); - if (resetCursor) { + if (options.resetCursor) { this.searchResultEditor.setSelection(new Range(1, 1, 1, 1)); this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 }); } - }, instant ? 0 : undefined); + }, options.delay); } } @@ -431,7 +437,7 @@ export class SearchEditor extends BaseTextEditor { const config = extractSearchQuery(header); this.toggleRunAgainMessage(body.getLineCount() === 1 && body.getValue() === '' && config.query !== ''); - this.queryEditorWidget.setValue(config.query, true); + this.queryEditorWidget.setValue(config.query); this.queryEditorWidget.searchInput.setCaseSensitive(config.caseSensitive); this.queryEditorWidget.searchInput.setRegex(config.regexp); this.queryEditorWidget.searchInput.setWholeWords(config.wholeWord); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index e07b696c2cf..7f1b02bff84 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -147,7 +147,7 @@ const openNewSearchEditor = const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; if (selected && configurationService.getValue('search').searchOnType) { - editor.runSearch(true, true); + editor.triggerSearch(); } }; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 9fef6bbe46e..09ba6cc1fa9 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -63,7 +63,7 @@ import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/com import * as TaskConfig from '../common/taskConfiguration'; import { TerminalTaskSystem } from './terminalTaskSystem'; -import { IQuickInputService, IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -80,7 +80,6 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { find } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IViewsService } from 'vs/workbench/common/views'; -import { ProviderProgressMananger } from 'vs/workbench/contrib/tasks/browser/providerProgressManager'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; @@ -221,7 +220,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _providers: Map; private _providerTypes: Map; protected _taskSystemInfos: Map; - private _providerProgressManager: ProviderProgressMananger | undefined; protected _workspaceTasksPromise?: Promise>; protected _areJsonTasksSupportedPromise: Promise = Promise.resolve(false); @@ -1349,24 +1347,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { + private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { return new Promise(async (resolve, reject) => { - let isDone = false; - let disposable: IDisposable | undefined; - const providePromise = provider.provideTasks(validTypes); - this._providerProgressManager?.addProvider(type, providePromise); - disposable = this._providerProgressManager?.canceled.token.onCancellationRequested(() => { - if (!isDone) { - resolve(); - } - }); - providePromise.then((value) => { - isDone = true; - disposable?.dispose(); + provider.provideTasks(validTypes).then((value) => { resolve(value); }, (e) => { - isDone = true; - disposable?.dispose(); reject(e); }); }); @@ -1378,11 +1363,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; validTypes['process'] = true; - this._providerProgressManager = new ProviderProgressMananger(); return new Promise(resolve => { let result: TaskSet[] = []; let counter: number = 0; - let done = (value: TaskSet | undefined) => { + let done = (value: TaskSet) => { if (value) { result.push(value); } @@ -2078,69 +2062,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return entries; }); - - const picker: IQuickPick = this.quickInputService.createQuickPick(); - picker.placeholder = placeHolder; - picker.matchOnDescription = true; - picker.ignoreFocusOut = true; - - picker.onDidTriggerItemButton(context => { - let task = context.item.task; - this.quickInputService.cancel(); - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); - } - }); - picker.busy = true; - const progressManager = this._providerProgressManager; - const progressTimeout = setTimeout(() => { - if (progressManager) { - progressManager.showProgress = (stillProviding, total) => { - let message = undefined; - if (stillProviding.length > 0) { - message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', ')); - } - picker.description = message; - }; - progressManager.addOnDoneListener(() => { - picker.focusOnInput(); - picker.customButton = false; - }); - if (!progressManager.isDone) { - picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting"); - picker.onDidCustom(() => { - this._providerProgressManager?.cancel(); - }); - picker.customButton = true; + return this.quickInputService.pick(pickEntries, { + placeHolder, + matchOnDescription: true, + onDidTriggerItemButton: context => { + let task = context.item.task; + this.quickInputService.cancel(); + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); } } - }, 1000); - pickEntries.then(entries => { - clearTimeout(progressTimeout); - progressManager?.dispose(); - picker.busy = false; - picker.items = entries; - }); - picker.show(); - - return new Promise(resolve => { - this._register(picker.onDidAccept(async () => { - let selection = picker.selectedItems ? picker.selectedItems[0] : undefined; - if (cancellationToken.isCancellationRequested) { - // canceled when there's only one task - const task = (await pickEntries)[0]; - if ((task).task) { - selection = task; - } + }, cancellationToken).then(async (selection) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await pickEntries)[0]; + if ((task).task) { + selection = task; } - picker.dispose(); - if (!selection) { - resolve(); - } - resolve(selection); - })); + } + if (!selection) { + return; + } + return selection; }); } diff --git a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts deleted file mode 100644 index 7c48da7f73c..00000000000 --- a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TaskSet } from 'vs/workbench/contrib/tasks/common/tasks'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; - -export class ProviderProgressMananger extends Disposable { - private _onProviderComplete: Emitter = new Emitter(); - private _stillProviding: Set = new Set(); - private _totalProviders: number = 0; - private _onDone: Emitter = new Emitter(); - private _isDone: boolean = false; - private _showProgress: ((remaining: string[], total: number) => void) | undefined; - public canceled: CancellationTokenSource = new CancellationTokenSource(); - - constructor() { - super(); - this._register(this._onProviderComplete.event(taskType => { - this._stillProviding.delete(taskType); - if (this._stillProviding.size === 0) { - this._isDone = true; - this._onDone.fire(); - } - if (this._showProgress) { - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - })); - } - - public addProvider(taskType: string, provider: Promise) { - this._totalProviders++; - this._stillProviding.add(taskType); - provider.then(() => this._onProviderComplete.fire(taskType)); - } - - public addOnDoneListener(onDoneListener: () => void) { - this._register(this._onDone.event(onDoneListener)); - } - - set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) { - this._showProgress = progressDisplayFunction; - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - - get isDone(): boolean { - return this._isDone; - } - - public cancel() { - this._isDone = true; - if (this._showProgress) { - this._showProgress([], 0); - } - this._onDone.fire(); - this.canceled.cancel(); - } -} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 21702db8313..df54d4357c9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -28,7 +28,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; @@ -46,6 +46,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { fromNow } from 'vs/base/common/date'; +import { IProductService } from 'vs/platform/product/common/productService'; const enum AuthStatus { Initializing = 'Initializing', @@ -76,13 +77,26 @@ type FirstTimeSyncClassification = { action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; +const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncService): string => { + if (userDataSyncService.status === SyncStatus.Syncing) { + return localize('sync is on with syncing', "{0} (syncing)", label); + } + if (userDataSyncService.lastSyncTime) { + return localize('sync is on with time', "{0} (synced {1})", label, fromNow(userDataSyncService.lastSyncTime, true)); + } + return label; +}; const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Sync: Turn off Sync") }; const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; -const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Sync: Show Activity") }; +const showSyncActivityCommand = { + id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { + return getActivityTitle(localize('show sync log', "Sync: Show Activity"), userDataSyncService); + } +}; const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -119,6 +133,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, + @IProductService private readonly productService: IProductService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -138,7 +153,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); - this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source))); + this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error))); this.registerActions(); this.initializeActiveAccount().then(_ => { if (!isWeb) { @@ -369,21 +384,38 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { - switch (code) { + private onAutoSyncError(error: UserDataSyncError): void { + switch (error.code) { + case UserDataSyncErrorCode.TurnedOff: + case UserDataSyncErrorCode.SessionExpired: + this.notificationService.notify({ + severity: Severity.Info, + message: localize('turned off', "Turned off sync because it was turned off from other device."), + actions: { + primary: [new Action('turn on sync', localize('Turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())] + } + }); + return; case UserDataSyncErrorCode.TooLarge: - if (source === SyncSource.Keybindings || source === SyncSource.Settings) { - const sourceArea = getSyncAreaLabel(source); + if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(error.source); this.notificationService.notify({ severity: Severity.Error, - message: localize('too large', "Disabled synchronizing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), + message: localize('too large', "Disabled sync {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), actions: { primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } return; + case UserDataSyncErrorCode.Incompatible: + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error incompatible', "Turned off sync because local data is incompatible with the data in the cloud. Please update {0} and turn on sync to continue syncing.", this.productService.nameLong), + }); + return; } } @@ -444,6 +476,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); + this.notificationService.info(localize('sync turned on', "Sync is turned on and from now on sycing will be done automatically.")); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { @@ -620,69 +653,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerActions(): void { this.registerTurnOnSyncAction(); - this.registerTurnOffSyncAction(); - - this.registerSyncStatusAction(); this.registerSignInAction(); this.registerShowSettingsConflictsAction(); this.registerShowKeybindingsConflictsAction(); + this.registerSyncStatusAction(); + this.registerTurnOffSyncAction(); this.registerConfigureSyncAction(); this.registerShowActivityAction(); this.registerShowSettingsAction(); } - private registerSyncStatusAction(): void { - const that = this; - this.syncStatusAction.value = registerAction2(class SyncStatusAction extends Action2 { - constructor() { - super({ - id: 'workbench.userData.actions.syncStatus', - get title() { - if (that.userDataSyncService.status === SyncStatus.Syncing) { - return localize('sync is on with syncing', "Sync is on (syncing)"); - } - if (that.userDataSyncService.lastSyncTime) { - return localize('sync is on with time', "Sync is on (synced {0})", fromNow(that.userDataSyncService.lastSyncTime, true)); - } - return localize('sync is on', "Sync is on"); - }, - menu: { - id: MenuId.GlobalActivity, - group: '5_sync', - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) - }, - }); - } - run(accessor: ServicesAccessor): any { - return new Promise((c, e) => { - const quickInputService = accessor.get(IQuickInputService); - const commandService = accessor.get(ICommandService); - const quickPick = quickInputService.createQuickPick(); - quickPick.items = [ - { id: configureSyncCommand.id, label: configureSyncCommand.title }, - { id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }, - { id: showSyncActivityCommand.id, label: showSyncActivityCommand.title }, - { type: 'separator' }, - { id: stopSyncCommand.id, label: stopSyncCommand.title } - ]; - const disposables = new DisposableStore(); - disposables.add(quickPick.onDidAccept(() => { - if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { - commandService.executeCommand(quickPick.selectedItems[0].id); - } - quickPick.hide(); - })); - disposables.add(quickPick.onDidHide(() => { - disposables.dispose(); - c(); - })); - quickPick.show(); - }); - } - }); - } - private registerTurnOnSyncAction(): void { const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => { @@ -701,6 +682,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('global activity turn on sync', "Turn on Sync...") }, when: turnOnSyncWhenContext, + order: 1 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: turnOnSyncCommand, @@ -716,32 +698,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private registerTurnOffSyncAction(): void { - const that = this; - registerAction2(class StopSyncAction extends Action2 { - constructor() { - super({ - id: stopSyncCommand.id, - title: stopSyncCommand.title, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }, - }); - } - async run(accessor: ServicesAccessor): Promise { - try { - await that.turnOff(); - } catch (e) { - if (!isPromiseCanceledError(e)) { - that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); - } - } - accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' }); - } - }); - } - private registerSignInAction(): void { const that = this; registerAction2(class StopSyncAction extends Action2 { @@ -753,6 +709,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', id: MenuId.GlobalActivity, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)), + order: 2 }, }); } @@ -776,6 +733,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveSettingsConflictsCommand.id, + title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + }, + when: resolveSettingsConflictsWhenContext, + order: 2 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: resolveSettingsConflictsCommand, @@ -793,6 +760,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveKeybindingsConflictsCommand.id, + title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + }, + when: resolveKeybindingsConflictsWhenContext, + order: 2 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: resolveKeybindingsConflictsCommand, @@ -801,6 +778,101 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } + private registerSyncStatusAction(): void { + const that = this; + const id = 'workbench.userData.actions.syncStatus'; + const title = localize('sync is on', "Sync is on"); + const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)); + this.syncStatusAction.value = registerAction2(class SyncStatusAction extends Action2 { + constructor() { + super({ + id, + get title() { + return getActivityTitle(localize('sync is on', "Sync is on"), that.userDataSyncService); + }, + menu: [ + { + id: MenuId.GlobalActivity, + group: '5_sync', + when, + order: 3 + } + ], + }); + } + run(accessor: ServicesAccessor): any { + return new Promise((c, e) => { + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const quickPick = quickInputService.createQuickPick(); + const items: Array = []; + if (that.userDataSyncService.conflictsSources.length) { + for (const source of that.userDataSyncService.conflictsSources) { + switch (source) { + case SyncSource.Settings: + items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); + break; + case SyncSource.Keybindings: + items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); + break; + } + } + items.push({ type: 'separator' }); + } + items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title }); + items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }); + items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) }); + items.push({ type: 'separator' }); + items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title }); + quickPick.items = items; + const disposables = new DisposableStore(); + disposables.add(quickPick.onDidAccept(() => { + if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { + commandService.executeCommand(quickPick.selectedItems[0].id); + } + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { id, title }, + when, + order: 3 + }); + } + + private registerTurnOffSyncAction(): void { + const that = this; + registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: stopSyncCommand.id, + title: stopSyncCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + async run(): Promise { + try { + await that.turnOff(); + } catch (e) { + if (!isPromiseCanceledError(e)) { + that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + } + } + } + }); + } + private registerConfigureSyncAction(): void { const that = this; registerAction2(class ShowSyncActivityAction extends Action2 { @@ -824,7 +896,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: showSyncActivityCommand.id, - title: showSyncActivityCommand.title, + title: showSyncActivityCommand.title(that.userDataSyncService), menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 926a6d2e7a2..1a637d676cf 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,11 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncUtilService, CONTEXT_SYNC_STATE, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IElectronService } from 'vs/platform/electron/node/electron'; class UserDataSyncServicesContribution implements IWorkbenchContribution { @@ -22,3 +28,23 @@ class UserDataSyncServicesContribution implements IWorkbenchContribution { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncServicesContribution, LifecyclePhase.Starting); + +registerAction2(class OpenSyncBackupsFolder extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.openSyncBackupsFolder', + title: localize('Open Backup folder', "Sync: Open Local Backups Folder"), + menu: { + id: MenuId.CommandPalette, + when: CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), + } + }); + } + async run(accessor: ServicesAccessor): Promise { + const syncHome = accessor.get(IEnvironmentService).userDataSyncHome; + const electronService = accessor.get(IElectronService); + const folderStat = await accessor.get(IFileService).resolve(syncHome); + const item = folderStat.children && folderStat.children[0] ? folderStat.children[0].resource : syncHome; + return electronService.showItemInFolder(item.fsPath); + } +}); diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index f1d8f9af253..bc750183d6e 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -341,6 +341,10 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + }, + 'force-color-profile': { + type: 'string', + markdownDescription: nls.localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.') } } }; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index f3c798f3b2d..9c352fdb917 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -14,7 +14,7 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename, isEqualOrParent, isEqual, joinPath } from 'vs/base/common/resources'; +import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; @@ -33,6 +33,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { indexOfPath } from 'vs/base/common/extpath'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -215,7 +216,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region File Changes: Move editors when detecting file move operations + //#region File Changes: Move & Deletes to move or close opend editors private onDidRunFileOperation(e: FileOperationEvent): void { @@ -230,24 +231,30 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private handleMovedFile(oldResource: URI, newResource: URI): void { + private onDidFilesChange(e: FileChangesEvent): void { + if (e.gotDeleted()) { + this.handleDeletedFile(e, true); + } + } + + private handleMovedFile(source: URI, target: URI): void { for (const group of this.editorGroupService.groups) { let replacements: (IResourceEditorReplacement | IEditorReplacement)[] = []; for (const editor of group.editors) { const resource = editor.resource; - if (!resource || !isEqualOrParent(resource, oldResource)) { + if (!resource || !isEqualOrParent(resource, source)) { continue; // not matching our resource } // Determine new resulting target resource let targetResource: URI; - if (oldResource.toString() === resource.toString()) { - targetResource = newResource; // file got moved + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved } else { const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); - const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase); - targetResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved + const index = indexOfPath(resource.path, source.path, ignoreCase); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved } // Delegate move() to editor instance @@ -256,12 +263,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return; // not target - ignore } - const extraOptions = { + const optionOverrides = { preserveFocus: true, pinned: group.isPinned(editor), index: group.getIndexOfEditor(editor), - inactive: !group.isActive(editor), - viewState: this.getViewStateFor(oldResource, group) + inactive: !group.isActive(editor) }; // Construct a replacement with our extra options mixed in @@ -271,7 +277,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { replacement: moveResult.editor, options: { ...moveResult.options, - ...extraOptions + ...optionOverrides } }); } else { @@ -281,7 +287,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { ...moveResult.editor, options: { ...(moveResult.editor as IResourceEditor /* TS fail */).options, - ...extraOptions + ...optionOverrides } } }); @@ -295,40 +301,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private getIndexOfPath(path: string, candidate: string, ignoreCase: boolean): number { - if (candidate.length > path.length) { - return -1; - } - - if (path === candidate) { - return 0; - } - - if (ignoreCase) { - path = path.toLowerCase(); - candidate = candidate.toLowerCase(); - } - - return path.indexOf(candidate); - } - - private getViewStateFor(resource: URI, group: IEditorGroup): IEditorViewState | undefined { - for (const editor of this.visibleControls) { - if (isEqual(editor.input.resource, resource) && editor.group === group) { - const control = editor.getControl(); - if (isCodeEditor(control)) { - return withNullAsUndefined(control.saveViewState()); - } - } - } - - return undefined; - } - - //#endregion - - //#region File Changes: Close editors of deleted files unless configured otherwise - private closeOnFileDelete: boolean = false; private fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { @@ -339,12 +311,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private onDidFilesChange(e: FileChangesEvent): void { - if (e.gotDeleted()) { - this.handleDeletedFile(e, true); - } - } - private handleDeletedFile(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { for (const editor of this.getAllNonDirtyEditors({ includeUntitled: false, supportSideBySide: true })) { (async () => { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 0e60ab8f43f..5c52f1a73b1 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -52,6 +52,8 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -103,6 +105,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService)); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); + instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 14572bc2df7..1c00289e458 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -67,6 +67,7 @@ export interface ISetting { enumDescriptions?: string[]; enumDescriptionsAreMarkdown?: boolean; tags?: string[]; + disallowSyncIgnore?: boolean; extensionInfo?: IConfigurationExtensionInfo; validator?: (value: any) => string | null; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 87d06698641..1efa6fd8cf9 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -636,6 +636,7 @@ export class DefaultSettings extends Disposable { enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions, enumDescriptionsAreMarkdown: !prop.enumDescriptions, tags: prop.tags, + disallowSyncIgnore: prop.disallowSyncIgnore, extensionInfo: extensionInfo, deprecationMessage: prop.deprecationMessage, validator: createValidator(prop) diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index 7976bb0fbdb..4dc5de8bc46 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -15,7 +15,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto _serviceBrand: undefined; private readonly channel: IChannel; - get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } + get onError(): Event { return Event.map(this.channel.listen('onError'), e => UserDataSyncError.toUserDataSyncError(e)); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 230a76fe556..9a8b2d91a6a 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -236,6 +236,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // Register all containers that were registered before this ctor this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + // Try generating all generated containers that don't need extensions + this.tryGenerateContainers(); + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(views, viewContainer))); this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); @@ -318,7 +321,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - private onDidRegisterExtensions(): void { + private tryGenerateContainers(fallbackToDefault?: boolean): void { for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { const containerId = containerInfo.containerId; @@ -337,20 +340,28 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - // check if view has been registered to default location - const viewContainer = this.viewsRegistry.getViewContainer(viewId); - const viewDescriptor = this.getViewDescriptor(viewId); - if (viewContainer && viewDescriptor) { - this.addViews(viewContainer, [viewDescriptor]); + if (fallbackToDefault) { + // check if view has been registered to default location + const viewContainer = this.viewsRegistry.getViewContainer(viewId); + const viewDescriptor = this.getViewDescriptor(viewId); + if (viewContainer && viewDescriptor) { + this.addViews(viewContainer, [viewDescriptor]); - const newLocation = this.getViewContainerLocation(viewContainer); - if (containerInfo.location && containerInfo.location !== newLocation) { - this._onDidChangeLocation.fire({ views: [viewDescriptor], from: containerInfo.location, to: newLocation }); + const newLocation = this.getViewContainerLocation(viewContainer); + if (containerInfo.location && containerInfo.location !== newLocation) { + this._onDidChangeLocation.fire({ views: [viewDescriptor], from: containerInfo.location, to: newLocation }); + } } } } - this.saveViewPositionsToCache(); + if (fallbackToDefault) { + this.saveViewPositionsToCache(); + } + } + + private onDidRegisterExtensions(): void { + this.tryGenerateContainers(true); } private onDidRegisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { diff --git a/src/vs/workbench/test/browser/api/extHostWebview.test.ts b/src/vs/workbench/test/browser/api/extHostWebview.test.ts index f08947b6b03..ed88cc6018e 100644 --- a/src/vs/workbench/test/browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWebview.test.ts @@ -13,6 +13,7 @@ import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/browser/api/mock'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; +import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; suite('ExtHostWebview', () => { @@ -24,7 +25,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -62,7 +63,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -103,7 +104,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index 1d013216ec7..24f0c27176f 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -22,6 +22,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; suite('MainThreadDocumentsAndEditors', () => { @@ -44,7 +45,7 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index a318ecd50c9..ef650b280c6 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -41,6 +41,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; suite('MainThreadEditors', () => { @@ -63,7 +64,7 @@ suite('MainThreadEditors', () => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); const services = new ServiceCollection(); diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 0073aa2f507..19ba3f55d7c 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -254,6 +254,37 @@ suite('Workbench base editor', () => { assert.ok(!memento.loadEditorState(testGroup0, URI.file('/E'))); }); + test('EditorMemento - move', function () { + const testGroup0 = new TestEditorGroupView(0); + + const editorGroupService = new TestEditorGroupsService([testGroup0]); + + interface TestViewState { line: number; } + + const rawMemento = Object.create(null); + let memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-1.txt'), { line: 1 }); + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-2.txt'), { line: 2 }); + memento.saveEditorState(testGroup0, URI.file('/some/other/file.txt'), { line: 3 }); + + memento.moveEditorState(URI.file('/some/folder/file-1.txt'), URI.file('/some/folder/file-moved.txt')); + + let res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-1.txt')); + assert.ok(!res); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-moved.txt')); + assert.equal(res?.line, 1); + + memento.moveEditorState(URI.file('/some/folder'), URI.file('/some/folder-moved')); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-moved.txt')); + assert.equal(res?.line, 1); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-2.txt')); + assert.equal(res?.line, 2); + }); + test('EditoMemento - use with editor input', function () { const testGroup0 = new TestEditorGroupView(0); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index a754831a0a1..849a327b502 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -18,6 +18,8 @@ import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { @@ -72,6 +74,7 @@ suite('Workbench editor model', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); + instantiationService.stub(IUndoRedoService, new UndoRedoService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index fb5e9e681da..9e00ead1308 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -91,6 +91,8 @@ import { IRemotePathService } from 'vs/workbench/services/path/common/remotePath import { Direction } from 'vs/base/browser/ui/grid/grid'; import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; export import TestContextService = CommonWorkbenchTestServices.TestContextService; @@ -194,6 +196,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); + instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); instantiationService.stub(IFileService, new TestFileService()); instantiationService.stub(IBackupFileService, new TestBackupFileService()); diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 10560e35590..2c5b03c33ed 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -33,6 +33,7 @@ import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workb import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; namespace Timer { export interface ITimerEvent { @@ -76,7 +77,7 @@ suite.skip('QuickOpen performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService())], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService(), new UndoRedoService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index ac995be33b1..4ccd31573b5 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -36,6 +36,7 @@ import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; // declare var __dirname: string; @@ -66,7 +67,7 @@ suite.skip('TextSearch performance (integration)', () => { [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, new UndoRedoService())], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index f13ada35b87..266a1a7a588 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -57,6 +57,7 @@ import 'vs/workbench/browser/parts/views/views'; //#region --- workbench services +import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; diff --git a/yarn.lock b/yarn.lock index e4f5accd3c7..ec7e792caac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9105,16 +9105,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" +typescript@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== + typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== - uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"