diff --git a/.yarnrc b/.yarnrc index 2c769cfba18..85baaa63a78 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "7.1.7" +target "6.1.6" runtime "electron" diff --git a/build/azure-pipelines/linux/xvfb.init b/build/azure-pipelines/linux/xvfb.init index 4d77d253a26..2365c09f3a4 100644 --- a/build/azure-pipelines/linux/xvfb.init +++ b/build/azure-pipelines/linux/xvfb.init @@ -19,7 +19,7 @@ [ "${NETWORKING}" = "no" ] && exit 0 PROG="/usr/bin/Xvfb" -PROG_OPTIONS=":10 -ac" +PROG_OPTIONS=":10 -ac -screen 0 1024x768x24" PROG_OUTPUT="/tmp/Xvfb.out" case "$1" in @@ -50,4 +50,4 @@ case "$1" in exit 1 esac -exit $RETVAL \ No newline at end of file +exit $RETVAL diff --git a/build/lib/extensions.js b/build/lib/extensions.js index c4385655eb9..e45b0d4e35a 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -27,6 +27,7 @@ const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const product = require('../../product.json'); function fromLocal(extensionPath) { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); const input = fs.existsSync(webpackFilename) @@ -184,8 +185,10 @@ const excludedExtensions = [ 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', + 'ms.vscode.js-debug-nightly' ]; -const builtInExtensions = require('../builtInExtensions.json'); +const builtInExtensions = require('../builtInExtensions.json') + .filter(({ forQualities }) => { var _a; return !product.quality || ((_a = forQualities === null || forQualities === void 0 ? void 0 : forQualities.includes) === null || _a === void 0 ? void 0 : _a.call(forQualities, product.quality)) !== false; }); function packageLocalExtensionsStream() { const localExtensionDescriptions = glob.sync('extensions/*/package.json') .map(manifestPath => { diff --git a/cgmanifest.json b/cgmanifest.json index e9dfd3d6ef3..c102a04f705 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf" + "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "78.0.3904.130" + "version": "76.0.3809.146" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a" + "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" } }, "isOnlyProductionDependency": true, - "version": "12.8.1" + "version": "12.4.0" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "bef0dd868b7d6d32716c319664ed480f2ae17396" + "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "7.1.7" + "version": "6.1.6" }, { "component": { diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 40d59e2bcd7..6a55968bda1 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -25,46 +25,6 @@ "editor.lineNumbers": "off" } }, - "commands": [ - { - "command": "searchResult.rerunSearch", - "title": "%searchResult.rerunSearch.title%", - "category": "Search Result", - "icon": { - "light": "./src/media/refresh-light.svg", - "dark": "./src/media/refresh-dark.svg" - } - }, - { - "command": "searchResult.rerunSearchWithContext", - "title": "%searchResult.rerunSearchWithContext.title%", - "category": "Search Result", - "icon": { - "light": "./src/media/refresh-light.svg", - "dark": "./src/media/refresh-dark.svg" - } - } - ], - "menus": { - "commandPalette": [ - { - "command": "searchResult.rerunSearch", - "when": "false" - }, - { - "command": "searchResult.rerunSearchWithContext", - "when": "false" - } - ], - "editor/title": [ - { - "command": "searchResult.rerunSearch", - "when": "editorLangId == search-result", - "alt": "searchResult.rerunSearchWithContext", - "group": "navigation" - } - ] - }, "languages": [ { "id": "search-result", diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json index ce90d23c09c..324fd97bcd2 100644 --- a/extensions/search-result/package.nls.json +++ b/extensions/search-result/package.nls.json @@ -1,6 +1,4 @@ { "displayName": "Search Result", - "description": "Provides syntax highlighting and language features for tabbed search results.", - "searchResult.rerunSearch.title": "Search Again", - "searchResult.rerunSearchWithContext.title": "Search Again (With Context)" + "description": "Provides syntax highlighting and language features for tabbed search results." } diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 0bd5e12188e..f7453a45d19 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -34,8 +34,6 @@ export function activate(context: vscode.ExtensionContext) { } context.subscriptions.push( - vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), - vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')), vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 26bca9f8614..df6b4e5ebb0 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -15,49 +15,120 @@ import { toBase64UrlEncoding } from './utils'; const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; const loginEndpointUrl = 'https://login.microsoftonline.com/'; const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; -const resourceId = 'https://management.core.windows.net/'; -const tenant = 'common'; +const tenant = 'organizations'; interface IToken { expiresIn: string; // How long access token is valid, in seconds accessToken: string; refreshToken: string; + + displayName: string; + scope: string; + sessionId: string; // The account id + the scope } interface ITokenClaims { + tid: string; email?: string; unique_name?: string; oid?: string; altsecid?: string; + scp: string; +} + +interface IStoredSession { + id: string; + refreshToken: string; + scope: string; // Scopes are alphabetized and joined with a space } export const onDidChangeSessions = new vscode.EventEmitter(); export class AzureActiveDirectoryService { - private _token: IToken | undefined; - private _refreshTimeout: NodeJS.Timeout | undefined; + private _tokens: IToken[] = []; + private _refreshTimeouts: Map = new Map(); public async initialize(): Promise { - const existingRefreshToken = await keychain.getToken(); - if (existingRefreshToken) { - await this.refreshToken(existingRefreshToken); + const storedData = await keychain.getToken(); + if (storedData) { + try { + const sessions = this.parseStoredData(storedData); + const refreshes = sessions.map(async session => { + try { + await this.refreshToken(session.refreshToken, session.scope); + } catch (e) { + await this.logout(session.id); + } + }); + + await Promise.all(refreshes); + } catch (e) { + await this.clearSessions(); + } } this.pollForChange(); } + private parseStoredData(data: string): IStoredSession[] { + return JSON.parse(data); + } + + private async storeTokenData(): Promise { + const serializedData: IStoredSession[] = this._tokens.map(token => { + return { + id: token.sessionId, + refreshToken: token.refreshToken, + scope: token.scope + }; + }); + + await keychain.setToken(JSON.stringify(serializedData)); + } + private pollForChange() { setTimeout(async () => { - const refreshToken = await keychain.getToken(); - // Another window has logged in, generate access token for this instance. - if (refreshToken && !this._token) { - await this.refreshToken(refreshToken); - onDidChangeSessions.fire(); + let didChange = false; + const storedData = await keychain.getToken(); + if (storedData) { + try { + const sessions = this.parseStoredData(storedData); + let promises = sessions.map(async session => { + const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); + if (!matchesExisting) { + try { + await this.refreshToken(session.refreshToken, session.scope); + didChange = true; + } catch (e) { + await this.logout(session.id); + } + } + }); + + promises = promises.concat(this._tokens.map(async token => { + const matchesExisting = sessions.some(session => token.scope === session.scope && token.sessionId === session.id); + if (!matchesExisting) { + await this.logout(token.sessionId); + didChange = true; + } + })); + + await Promise.all(promises); + } catch (e) { + Logger.error(e.message); + // if data is improperly formatted, remove all of it and send change event + this.clearSessions(); + didChange = true; + } + } else { + if (this._tokens.length) { + // Log out all + await this.clearSessions(); + didChange = true; + } } - // Another window has logged out - if (!refreshToken && this._token) { - await this.logout(); + if (didChange) { onDidChangeSessions.fire(); } @@ -65,28 +136,29 @@ export class AzureActiveDirectoryService { }, 1000 * 30); } - private tokenToAccount(token: IToken): vscode.Session { - const claims = this.getTokenClaims(token.accessToken); + private convertToSession(token: IToken): vscode.Session { return { - id: claims?.oid || claims?.altsecid || '', + id: token.sessionId, accessToken: token.accessToken, - displayName: claims?.email || claims?.unique_name || 'user@example.com' + displayName: token.displayName, + scopes: token.scope.split(' ') }; } - private getTokenClaims(accessToken: string): ITokenClaims | undefined { + private getTokenClaims(accessToken: string): ITokenClaims { try { return JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); } catch (e) { Logger.error(e.message); + throw new Error('Unable to read token claims'); } } get sessions(): vscode.Session[] { - return this._token ? [this.tokenToAccount(this._token)] : []; + return this._tokens.map(token => this.convertToSession(token)); } - public async login(): Promise { + public async login(scope: string): Promise { Logger.info('Logging in...'); const nonce = crypto.randomBytes(16).toString('base64'); const { server, redirectPromise, codePromise } = createServer(nonce); @@ -112,7 +184,7 @@ export class AzureActiveDirectoryService { const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); - const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&resource=${encodeURIComponent(resourceId)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; + const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; await redirectReq.res.writeHead(302, { Location: loginUrl }); redirectReq.res.end(); @@ -124,8 +196,8 @@ export class AzureActiveDirectoryService { if ('err' in codeRes) { throw codeRes.err; } - token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); - this.setToken(token); + token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope); + this.setToken(token, scope); Logger.info('Login successful'); res.writeHead(302, { Location: '/' }); res.end(); @@ -141,27 +213,46 @@ export class AzureActiveDirectoryService { } } - private async setToken(token: IToken): Promise { - this._token = token; - - if (this._refreshTimeout) { - clearTimeout(this._refreshTimeout); + private async setToken(token: IToken, scope: string): Promise { + const existingToken = this._tokens.findIndex(t => t.sessionId === token.sessionId); + if (existingToken) { + this._tokens.splice(existingToken, 1, token); + } else { + this._tokens.push(token); } - this._refreshTimeout = setTimeout(async () => { + const existingTimeout = this._refreshTimeouts.get(token.sessionId); + if (existingTimeout) { + clearTimeout(existingTimeout); + } + + this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { try { - await this.refreshToken(token.refreshToken); + await this.refreshToken(token.refreshToken, scope); } catch (e) { - await this.logout(); + await this.logout(token.sessionId); } finally { onDidChangeSessions.fire(); } - }, 1000 * (parseInt(token.expiresIn) - 10)); + }, 1000 * (parseInt(token.expiresIn) - 10))); - await keychain.setToken(token.refreshToken); + this.storeTokenData(); } - private async exchangeCodeForToken(code: string, codeVerifier: string): Promise { + private getTokenFromResponse(buffer: Buffer[], scope: string): IToken { + const json = JSON.parse(Buffer.concat(buffer).toString()); + const claims = this.getTokenClaims(json.access_token); + return { + expiresIn: json.expires_in, + accessToken: json.access_token, + refreshToken: json.refresh_token, + scope, + sessionId: claims.tid + (claims.oid || claims.altsecid) + scope, + displayName: claims.email || claims.unique_name || 'user@example.com' + }; + } + + private async exchangeCodeForToken(code: string, codeVerifier: string, scope: string): Promise { return new Promise((resolve: (value: IToken) => void, reject) => { Logger.info('Exchanging login code for token'); try { @@ -169,12 +260,12 @@ export class AzureActiveDirectoryService { grant_type: 'authorization_code', code: code, client_id: clientId, - resource: resourceId, + scope: scope, code_verifier: codeVerifier, redirect_uri: redirectUrl }); - const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/token`); + const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`); const post = https.request({ host: tokenUrl.authority, @@ -191,12 +282,7 @@ export class AzureActiveDirectoryService { }); result.on('end', () => { if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - resolve({ - expiresIn: json.expires_in, - accessToken: json.access_token, - refreshToken: json.refresh_token - }); + resolve(this.getTokenFromResponse(buffer, scope)); } else { reject(new Error('Unable to login.')); } @@ -217,19 +303,19 @@ export class AzureActiveDirectoryService { }); } - private async refreshToken(refreshToken: string): Promise { + private async refreshToken(refreshToken: string, scope: string): Promise { return new Promise((resolve: (value: IToken) => void, reject) => { Logger.info('Refreshing token...'); const postData = querystring.stringify({ refresh_token: refreshToken, client_id: clientId, grant_type: 'refresh_token', - resource: resourceId + scope: scope }); const post = https.request({ host: 'login.microsoftonline.com', - path: `/${tenant}/oauth2/token`, + path: `/${tenant}/oauth2/v2.0/token`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -242,17 +328,11 @@ export class AzureActiveDirectoryService { }); result.on('end', async () => { if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - const token = { - expiresIn: json.expires_in, - accessToken: json.access_token, - refreshToken: json.refresh_token - }; - this.setToken(token); + const token = this.getTokenFromResponse(buffer, scope); + this.setToken(token, scope); Logger.info('Token refresh success'); resolve(token); } else { - await this.logout(); Logger.error('Refreshing token failed'); reject(new Error('Refreshing token failed.')); } @@ -269,12 +349,35 @@ export class AzureActiveDirectoryService { }); } - public async logout() { - Logger.info('Logging out'); - delete this._token; - await keychain.deleteToken(); - if (this._refreshTimeout) { - clearTimeout(this._refreshTimeout); + public async logout(sessionId: string) { + Logger.info(`Logging out of session '${sessionId}'`); + const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); + if (tokenIndex > -1) { + this._tokens.splice(tokenIndex, 1); + } + + if (this._tokens.length === 0) { + await keychain.deleteToken(); + } else { + this.storeTokenData(); + } + + const timeout = this._refreshTimeouts.get(sessionId); + if (timeout) { + clearTimeout(timeout); + this._refreshTimeouts.delete(sessionId); } } + + public async clearSessions() { + Logger.info('Logging out of all sessions'); + this._tokens = []; + await keychain.deleteToken(); + + this._refreshTimeouts.forEach(timeout => { + clearTimeout(timeout); + }); + + this._refreshTimeouts.clear(); + } } diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 5e71ed3d97a..83988aa7c18 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -17,9 +17,9 @@ export async function activate(context: vscode.ExtensionContext) { displayName: 'Microsoft', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), - login: async () => { + login: async (scopes: string[]) => { try { - await loginService.login(); + await loginService.login(scopes.sort().join(' ')); return loginService.sessions[0]!; } catch (e) { vscode.window.showErrorMessage(`Logging in failed: ${e}`); @@ -27,7 +27,7 @@ export async function activate(context: vscode.ExtensionContext) { } }, logout: async (id: string) => { - return loginService.logout(); + return loginService.logout(id); } }); diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts index 6eca3720fb3..d6ad851d3dd 100644 --- a/extensions/vscode-account/src/vscode.proposed.d.ts +++ b/extensions/vscode-account/src/vscode.proposed.d.ts @@ -20,6 +20,7 @@ declare module 'vscode' { id: string; accessToken: string; displayName: string; + scopes: string[] } export interface AuthenticationProvider { @@ -35,7 +36,7 @@ declare module 'vscode' { /** * Prompts a user to login. */ - login(): Promise; + login(scopes: string[]): Promise; logout(sessionId: string): Promise; } @@ -48,13 +49,7 @@ declare module 'vscode' { export const onDidRegisterAuthenticationProvider: Event; export const onDidUnregisterAuthenticationProvider: Event; - /** - * Fires with the provider id that changed sessions. - */ - export const onDidChangeSessions: Event; - export function login(providerId: string): Promise; - export function logout(providerId: string, accountId: string): Promise; - export function getSessions(providerId: string): Promise | undefined>; + export const providers: ReadonlyArray; } // #region Ben - extension auth flow (desktop+web) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 739ce386371..fba9348435c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -105,7 +105,7 @@ suite('commands namespace tests', () => { }); test('api-command: vscode.open', function () { - let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/far.js'); + let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/image.png'); let a = commands.executeCommand('vscode.open', uri).then(() => assert.ok(true), () => assert.ok(false)); let b = commands.executeCommand('vscode.open', uri, ViewColumn.Two).then(() => assert.ok(true), () => assert.ok(false)); let c = commands.executeCommand('vscode.open').then(() => assert.ok(false), () => assert.ok(true)); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index b59d91ff380..e785f1d4afb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,8 +13,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -// TODO: Re-enable after https://github.com/microsoft/vscode/issues/88415 -suite.skip('Webview tests', () => { +suite('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts index 7d552df04a8..f3c69fbbe67 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -140,4 +140,42 @@ suite('workspace-fs', () => { assert.equal(e.name, vscode.FileSystemError.Unavailable().name); } }); + + test('vscode.workspace.fs.remove() (and copy()) succeed unexpectedly. #84177', async function () { + const entries = await vscode.workspace.fs.readDirectory(root); + assert.ok(entries.length > 0); + + const someFolder = root.with({ path: posix.join(root.path, '6b1f9d664a92') }); + + try { + await vscode.workspace.fs.delete(someFolder, { recursive: true }); + assert.ok(false); + } catch (err) { + assert.ok(true); + } + }); + + test('vscode.workspace.fs.remove() (and copy()) succeed unexpectedly. #84177', async function () { + const entries = await vscode.workspace.fs.readDirectory(root); + assert.ok(entries.length > 0); + + const folder = root.with({ path: posix.join(root.path, 'folder') }); + const file = root.with({ path: posix.join(root.path, 'folder/file') }); + + await vscode.workspace.fs.createDirectory(folder); + await vscode.workspace.fs.writeFile(file, Buffer.from('FOO')); + + const someFolder = root.with({ path: posix.join(root.path, '6b1f9d664a92/a564c52da70a') }); + + try { + await vscode.workspace.fs.copy(folder, someFolder, { overwrite: true }); + assert.ok(true); + } catch (err) { + assert.ok(false, err); + + } finally { + await vscode.workspace.fs.delete(folder, { recursive: true, useTrash: false }); + await vscode.workspace.fs.delete(someFolder, { recursive: true, useTrash: false }); + } + }); }); diff --git a/package.json b/package.json index f13416fb9a2..22ae11f83fe 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "7.1.7", + "electron": "6.1.6", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index 7f2761f808a..b5d215dc129 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -749,13 +749,6 @@ export class QuickOpenWidget extends Disposable implements IModelProvider, IThem this.tree.focusNth(1); } } - - // Finally check for auto focus of last entry - else if (autoFocus.autoFocusLastEntry) { - if (entries.length > 1) { - this.tree.focusLast(); - } - } } refresh(input?: IModel, autoFocus?: IAutoFocus): void { diff --git a/src/vs/base/parts/quickopen/common/quickOpen.ts b/src/vs/base/parts/quickopen/common/quickOpen.ts index 582ddf56ee6..4b695fc34af 100644 --- a/src/vs/base/parts/quickopen/common/quickOpen.ts +++ b/src/vs/base/parts/quickopen/common/quickOpen.ts @@ -26,11 +26,6 @@ export interface IAutoFocus { */ autoFocusSecondEntry?: boolean; - /** - * If set to true, will automatically select the last entry from the result list. - */ - autoFocusLastEntry?: boolean; - /** * If set to true, will automatically select any entry whose label starts with the search * value. Since some entries to the top might match the query but not on the prefix, this diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 712d6f05c5b..4d544450cc8 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (_event: Event, contents) => { contents.on('will-attach-webview', (event: Event, webPreferences, params) => { - const isValidWebviewSource = (source: string | undefined): boolean => { + const isValidWebviewSource = (source: string): boolean => { if (!source) { return false; } @@ -191,12 +191,11 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - // https://github.com/electron/electron/issues/21553 - if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) { + if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) { return; } - delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 + delete webPreferences.preloadUrl; // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -498,27 +497,27 @@ export class CodeApplication extends Disposable { this.logService.info(`Tracing: waiting for windows to get ready...`); let recordingStopped = false; - const stopRecording = async (timeout: boolean) => { + const stopRecording = (timeout: boolean) => { if (recordingStopped) { return; } recordingStopped = true; // only once - const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`)); - - if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { + if (!timeout) { + if (this.dialogMainService) { + this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + } + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); } - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); - } + }); }; // Wait up to 30s before creating the trace anyways diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 1f1c08a58f1..d0ea8752aae 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -347,9 +347,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { - const responseHeaders = details.responseHeaders as Record; + const responseHeaders = details.responseHeaders as { [key: string]: string[] }; - const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); + const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); } @@ -441,7 +441,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); } private onWindowError(error: WindowError): void { @@ -648,7 +648,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (windowConfig?.autoDetectHighContrast === false) { autoDetectHighContrast = false; } - windowConfiguration.highContrast = isWindows && autoDetectHighContrast && nativeTheme.shouldUseInvertedColorScheme; + windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related @@ -1007,22 +1007,22 @@ export class CodeWindow extends Disposable implements ICodeWindow { switch (visibility) { case ('default'): this._win.setMenuBarVisibility(!isFullscreen); - this._win.autoHideMenuBar = isFullscreen; + this._win.setAutoHideMenuBar(isFullscreen); break; case ('visible'): this._win.setMenuBarVisibility(true); - this._win.autoHideMenuBar = false; + this._win.setAutoHideMenuBar(false); break; case ('toggle'): this._win.setMenuBarVisibility(false); - this._win.autoHideMenuBar = true; + this._win.setAutoHideMenuBar(true); break; case ('hidden'): this._win.setMenuBarVisibility(false); - this._win.autoHideMenuBar = false; + this._win.setAutoHideMenuBar(false); break; } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 9de657dcd87..93c7b9bcef4 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2668,6 +2668,10 @@ export interface ISuggestOptions { * Show snippet-suggestions. */ showSnippets?: boolean; + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + hideStatusBar?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -2709,6 +2713,7 @@ class EditorSuggest extends BaseEditorOption { if (this._model) { this._model.move(true, true); } }), new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem codicon-chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }) ]; - this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService, this._configurationService); + this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService); this._widgetVisible.set(true); this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 5190682a7c9..7cbc0b2e504 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -27,10 +27,6 @@ import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/action import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OperatingSystem, OS } from 'vs/base/common/platform'; - -type ModifierKey = 'meta' | 'ctrl' | 'alt'; class MessageWidget { @@ -44,7 +40,6 @@ class MessageWidget { private readonly _relatedDiagnostics = new WeakMap(); private readonly _disposables: DisposableStore = new DisposableStore(); - private _clickModifierKey: ModifierKey; private _codeLink?: HTMLElement; constructor( @@ -52,7 +47,6 @@ class MessageWidget { editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void, private readonly _openerService: IOpenerService, - private readonly _configurationService: IConfigurationService ) { this._editor = editor; @@ -88,16 +82,6 @@ class MessageWidget { domNode.style.top = `-${e.scrollTop}px`; })); this._disposables.add(this._scrollable); - - this._clickModifierKey = this._getClickModifierKey(); - this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.multiCursorModifier')) { - this._clickModifierKey = this._getClickModifierKey(); - if (this._codeLink) { - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); - } - } - })); } dispose(): void { @@ -150,15 +134,12 @@ class MessageWidget { detailsElement.appendChild(codeElement); } else { this._codeLink = dom.$('a.code-link'); - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); this._codeLink.setAttribute('href', `${code.link.toString()}`); this._codeLink.onclick = (e) => { + this._openerService.open(code.link); e.preventDefault(); - if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { - this._openerService.open(code.link); - e.stopPropagation(); - } + e.stopPropagation(); }; const codeElement = dom.append(this._codeLink, dom.$('span')); @@ -211,31 +192,6 @@ class MessageWidget { getHeightInLines(): number { return Math.min(17, this._lines); } - - private _getClickModifierKey(): ModifierKey { - const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); - if (value === 'ctrlCmd') { - return 'alt'; - } else { - if (OS === OperatingSystem.Macintosh) { - return 'meta'; - } else { - return 'ctrl'; - } - } - } - - private _getCodelinkTooltip(): string { - const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); - const tooltipKeybinding = this._clickModifierKey === 'ctrl' - ? nls.localize('links.navigate.kb.meta', 'ctrl + click') - : - this._clickModifierKey === 'meta' - ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') - : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); - - return `${tooltipLabel} (${tooltipKeybinding})`; - } } export class MarkerNavigationWidget extends PeekViewWidget { @@ -256,8 +212,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { editor: ICodeEditor, private readonly actions: ReadonlyArray, private readonly _themeService: IThemeService, - private readonly _openerService: IOpenerService, - private readonly _configurationService: IConfigurationService + private readonly _openerService: IOpenerService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); this._severity = MarkerSeverity.Warning; @@ -327,7 +282,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._container = document.createElement('div'); container.appendChild(this._container); - this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService, this._configurationService); + this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService); this._disposables.add(this._message); } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 7678aab4664..450930c957f 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -300,7 +300,7 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC } KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'changePeekFocus', + id: 'togglePeekWidgetFocus', weight: KeybindingWeight.EditorContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.F2), when: ContextKeyExpr.or(ctxReferenceSearchVisible, PeekContext.inPeekEditor), diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index b77e2b0132a..3a0f4eea252 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -27,7 +27,6 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class ModesHoverController implements IEditorContribution { @@ -67,8 +66,7 @@ export class ModesHoverController implements IEditorContribution { @IModeService private readonly _modeService: IModeService, @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IThemeService private readonly _themeService: IThemeService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IThemeService private readonly _themeService: IThemeService ) { this._isMouseDown = false; this._hoverClicked = false; @@ -207,7 +205,7 @@ export class ModesHoverController implements IEditorContribution { } private _createHoverWidgets() { - this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService, this._configurationService); + this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); this._glyphWidget.value = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index c52183db542..7a3ed0db358 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -39,8 +39,6 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OperatingSystem, OS } from 'vs/base/common/platform'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; const $ = dom.$; @@ -194,8 +192,6 @@ const markerCodeActionTrigger: CodeActionTrigger = { filter: { include: CodeActionKind.QuickFix } }; -type ModifierKey = 'meta' | 'ctrl' | 'alt'; - export class ModesContentHoverWidget extends ContentHoverWidget { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -209,7 +205,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; - private _clickModifierKey: ModifierKey; private _codeLink?: HTMLElement; private readonly renderDisposable = this._register(new MutableDisposable()); @@ -221,7 +216,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private readonly _keybindingService: IKeybindingService, private readonly _modeService: IModeService, private readonly _openerService: IOpenerService = NullOpenerService, - private readonly _configurationService: IConfigurationService ) { super(ModesContentHoverWidget.ID, editor); @@ -258,16 +252,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._renderMessages(this._lastRange, this._messages); } })); - - this._clickModifierKey = this._getClickModifierKey(); - this._register((this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.multiCursorModifier')) { - this._clickModifierKey = this._getClickModifierKey(); - if (this._codeLink) { - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); - } - } - }))); } dispose(): void { @@ -532,15 +516,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget { sourceElement.innerText = source; } this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); this._codeLink.setAttribute('href', code.link.toString()); this._codeLink.onclick = (e) => { + this._openerService.open(code.link); e.preventDefault(); - if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { - this._openerService.open(code.link); - e.stopPropagation(); - } + e.stopPropagation(); }; const codeElement = dom.append(this._codeLink, $('span')); @@ -671,31 +652,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); - - private _getClickModifierKey(): ModifierKey { - const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); - if (value === 'ctrlCmd') { - return 'alt'; - } else { - if (OS === OperatingSystem.Macintosh) { - return 'meta'; - } else { - return 'ctrl'; - } - } - } - - private _getCodelinkTooltip(): string { - const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); - const tooltipKeybinding = this._clickModifierKey === 'ctrl' - ? nls.localize('links.navigate.kb.meta', 'ctrl + click') - : - this._clickModifierKey === 'meta' - ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') - : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); - - return `${tooltipLabel} (${tooltipKeybinding})`; - } } function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 432fbf09650..20f98ee066c 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -111,6 +111,49 @@ font-weight: bold; } +/** Status Bar **/ + +.monaco-editor .suggest-widget > .tree { + margin-bottom: 18px; +} +.monaco-editor .suggest-widget > .suggest-status-bar { + visibility: hidden; + + position: absolute; + left: 0; + + box-sizing: border-box; + + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + + width: 100%; + + font-size: 80%; + + border-left-width: 1px; + border-left-style: solid; + border-right-width: 1px; + border-right-style: solid; + border-bottom-width: 1px; + border-bottom-style: solid; + + padding: 1px 8px 1px 4px; + + box-shadow: 0 -.5px 3px #ddd; +} +.monaco-editor .suggest-widget > .suggest-status-bar span { + opacity: 0.7; +} +.monaco-editor .suggest-widget.list-right.docs-side > .suggest-status-bar { + left: auto; + right: 0; +} +.monaco-editor .suggest-widget.docs-side > .suggest-status-bar { + width: 50%; +} + /** ReadMore Icon styles **/ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close, diff --git a/src/vs/editor/contrib/suggest/media/suggestStatusBar.css b/src/vs/editor/contrib/suggest/media/suggestStatusBar.css new file mode 100644 index 00000000000..cf7b3924f58 --- /dev/null +++ b/src/vs/editor/contrib/suggest/media/suggestStatusBar.css @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar { + visibility: visible; +} + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar span { + min-height: 18px; +} + +.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore, +.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused > .contents > .main > .right:not(.always-show-details) > .readMore { + display: none; +} + +.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover > .contents > .main > .right.can-expand-details > .details-label { + width: 100%; +} diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 719c5446138..f54f304688c 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -43,7 +43,7 @@ import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeH * Stop suggest widget from disappearing when clicking into other areas * For development purpose only */ -const _sticky = false; +const _sticky = true; class LineSuffix { @@ -528,11 +528,8 @@ const SuggestCommand = EditorCommand.bindToContribution(Sugge registerEditorCommand(new SuggestCommand({ id: 'acceptSelectedSuggestion', precondition: SuggestContext.Visible, - handler(x, args) { - const alternative: boolean = typeof args === 'object' && typeof args.alternative === 'boolean' - ? args.alternative - : false; - x.acceptSelectedSuggestion(true, alternative); + handler(x) { + x.acceptSelectedSuggestion(true, false); } })); @@ -552,16 +549,23 @@ KeybindingsRegistry.registerKeybindingRule({ weight }); +// todo@joh control enablement via context key // shift+enter and shift+tab use the alternative-flag so that the suggest controller // is doing the opposite of the editor.suggest.overwriteOnAccept-configuration -KeybindingsRegistry.registerKeybindingRule({ - id: 'acceptSelectedSuggestion', - when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), - primary: KeyMod.Shift | KeyCode.Tab, - secondary: [KeyMod.Shift | KeyCode.Enter], - args: { alternative: true }, - weight -}); +registerEditorCommand(new SuggestCommand({ + id: 'acceptAlternativeSelectedSuggestion', + precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), + kbOpts: { + weight: weight, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.Shift | KeyCode.Enter, + secondary: [KeyMod.Shift | KeyCode.Tab], + }, + handler(x) { + x.acceptSelectedSuggestion(false, true); + }, +})); + // continue to support the old command CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion'); diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 3f6f7a3b4f1..95c1a8c24f9 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/suggest'; +import 'vs/css!./media/suggestStatusBar'; import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; @@ -41,6 +42,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { flatten } from 'vs/base/common/arrays'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Position } from 'vs/editor/common/core/position'; const expandSuggestionDocsByDefault = false; @@ -301,7 +303,7 @@ class SuggestionDetails { private readonly widget: SuggestWidget, private readonly editor: ICodeEditor, private readonly markdownRenderer: MarkdownRenderer, - private readonly triggerKeybindingLabel: string, + private readonly kbToggleDetails: string, ) { this.disposables = new DisposableStore(); @@ -316,7 +318,7 @@ class SuggestionDetails { this.header = append(this.body, $('.header')); this.close = append(this.header, $('span.codicon.codicon-close')); - this.close.title = nls.localize('readLess', "Read less...{0}", this.triggerKeybindingLabel); + this.close.title = nls.localize('readLess', "Read less...{0}", this.kbToggleDetails); this.type = append(this.header, $('p.type')); this.docs = append(this.body, $('p.docs')); @@ -468,6 +470,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate; private listHeight?: number; @@ -517,18 +525,20 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate toggleClass(this.element, 'with-status-bar', !this.editor.getOption(EditorOption.suggest).hideStatusBar); + applyStatusBarStyle(); + + this.statusBarElement = append(this.element, $('.suggest-status-bar')); + this.statusBarLeftSpan = append(this.statusBarElement, $('span')); + this.statusBarRightSpan = append(this.statusBarElement, $('span')); + + this.setStatusBarLeftText(''); + this.setStatusBarRightText(''); + + this.details = instantiationService.createInstance(SuggestionDetails, this.element, this, this.editor, markdownRenderer, kbToggleDetails); const applyIconStyle = () => toggleClass(this.element, 'no-icons', !this.editor.getOption(EditorOption.suggest).showIcons); applyIconStyle(); - let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, triggerKeybindingLabel); + let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, kbToggleDetails); this.list = new List('SuggestWidget', this.listElement, this, [renderer], { useShadows: false, @@ -582,7 +603,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onListSelection(e))); this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e))); this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); - this.toDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { applyIconStyle(); } })); + this.toDispose.add(this.editor.onDidChangeConfiguration(e => { + if (e.hasChanged(EditorOption.suggest)) { + applyStatusBarStyle(); + applyIconStyle(); + } + })); this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService); this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService); @@ -661,12 +687,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this should a toolbar with actions so that these things become + // mouse clickable and fit for accessibility... + const wantsInsert = this.editor.getOption(EditorOption.suggest).insertMode === 'insert'; + const kbAccept = this.keybindingService.lookupKeybinding('acceptSelectedSuggestion')?.getLabel(); + const kbAcceptAlt = this.keybindingService.lookupKeybinding('acceptAlternativeSelectedSuggestion')?.getLabel(); + if (!Position.equals(item.editInsertEnd, item.editReplaceEnd)) { + // insert AND replace + if (wantsInsert) { + this.setStatusBarLeftText(nls.localize('insert', "{0} to insert, {1} to replace", kbAccept, kbAcceptAlt)); + } else { + this.setStatusBarLeftText(nls.localize('replace', "{0} to replace, {1} to insert", kbAccept, kbAcceptAlt)); + } + } else { + this.setStatusBarLeftText(nls.localize('accept', "{0} to accept", kbAccept)); + } if (this.currentSuggestionDetails) { this.currentSuggestionDetails.cancel(); @@ -739,6 +783,16 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate>; diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogs.ts index c694c003e9b..7b49ca50c2e 100644 --- a/src/vs/platform/dialogs/electron-main/dialogs.ts +++ b/src/vs/platform/dialogs/electron-main/dialogs.ts @@ -173,7 +173,7 @@ export class DialogMainService implements IDialogMainService { showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise { - function normalizePaths(paths: string[]): string[] { + function normalizePaths(paths: string[] | undefined): string[] | undefined { if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index bae55607623..e0beffc55ed 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -18,6 +18,7 @@ import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { NativeImage } from 'electron'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; @@ -66,7 +67,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } const webContents = window.win.webContents; - const image = await webContents.capturePage(); + const image = await new Promise(c => webContents.capturePage(c)); return image.toPNG().toString('base64'); } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 98b0e2a1f66..24ef0a029ab 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -367,13 +367,15 @@ export class ElectronMainService implements IElectronMainService { //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { - const window = this.windowById(windowId); - const session = window?.win?.webContents?.session; - if (session) { - return session.resolveProxy(url); - } else { - return undefined; - } + return new Promise(resolve => { + const window = this.windowById(windowId); + const session = window?.win?.webContents?.session; + if (session) { + session.resolveProxy(url, proxy => resolve(proxy)); + } else { + resolve(); + } + }); } //#endregion diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 6f10b476dc8..93429296cfe 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -64,7 +64,7 @@ export interface INeverShowAgainOptions { isSecondary?: boolean; /** - * Wether to persist the choice in the current workspace or for all workspaces. By + * Whether to persist the choice in the current workspace or for all workspaces. By * default it will be persisted for all workspaces. */ scope?: NeverShowAgainScope; @@ -192,7 +192,7 @@ export interface IPromptChoice { isSecondary?: boolean; /** - * Wether to keep the notification open after the choice was selected + * Whether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. */ keepOpen?: boolean; diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 2c8860d6c69..d5afc64d3bf 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -104,6 +104,7 @@ export interface TokenStylingDefaultRule { export interface TokenStylingRule { match(classification: TokenClassification): number; value: TokenStyle; + selector: TokenClassification; } /** @@ -294,7 +295,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { public getTokenStylingRule(selector: TokenClassification, value: TokenStyle): TokenStylingRule { return { match: this.newMatcher(selector), - value + value, + selector }; } diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index 6787d57bc27..8cd6ecdcaa7 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { ipcMain as ipc, nativeTheme } from 'electron'; +import { systemPreferences, ipcMain as ipc } from 'electron'; import { IStateService } from 'vs/platform/state/node/state'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -42,14 +42,14 @@ export class ThemeMainService implements IThemeMainService { } getBackgroundColor(): string { - if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { + if (isWindows && systemPreferences.isInvertedColorScheme()) { return DEFAULT_BG_HC_BLACK; } let background = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); if (!background) { let baseTheme: string; - if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { + if (isWindows && systemPreferences.isInvertedColorScheme()) { baseTheme = 'hc-black'; } else { baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index ded00afd7ce..53f53612572 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,30 +4,87 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { toLocalISOString } from 'vs/base/common/date'; import { ThrottledDelayer } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; export abstract class AbstractSynchroniser extends Disposable { protected readonly syncFolder: URI; private cleanUpDelayer: ThrottledDelayer; + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + protected readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + protected readonly lastSyncResource: URI; + constructor( readonly source: SyncSource, @IFileService protected readonly fileService: IFileService, - @IEnvironmentService environmentService: IEnvironmentService + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); + this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`); this.cleanUpDelayer = new ThrottledDelayer(50); } + protected setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemoteData(): Promise { + const remoteUserData = await this.getRemoteUserData(); + return remoteUserData.content !== null; + } + + async resetLocal(): Promise { + try { + await this.fileService.del(this.lastSyncResource); + } catch (e) { /* ignore */ } + } + + protected async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + protected async updateLastSyncUserData(lastSyncUserData: T): Promise { + await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); + } + + protected getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData || null, this.source); + } + + protected async updateRemoteUserData(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source); + } + protected async backupLocal(content: VSBuffer): Promise { const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); await this.fileService.writeFile(resource, content); @@ -43,4 +100,40 @@ export abstract class AbstractSynchroniser extends Disposable { } } + protected abstract getRemoteDataResourceKey(): string; +} + +export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { + + constructor( + protected readonly file: URI, + readonly source: SyncSource, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + ) { + super(source, fileService, environmentService, userDataSyncStoreService); + this._register(this.fileService.watch(dirname(file))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(file))(() => this._onDidChangeLocal.fire())); + } + + protected async getLocalFileContent(): Promise { + try { + return await this.fileService.readFile(this.file); + } catch (error) { + return null; + } + } + + protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.backupLocal(oldContent.value); + await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d02dd14a69a..95d03f6bc0b 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,17 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter, Event } from 'vs/base/common/event'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; -import { Queue } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; @@ -35,32 +31,17 @@ interface ILastSyncUserData extends IUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions'; - - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncExtensionsResource: URI; - private readonly replaceQueue: Queue; - constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.Extensions, fileService, environmentService); - this.replaceQueue = this._register(new Queue()); - this.lastSyncExtensionsResource = joinPath(this.syncFolder, '.lastSyncExtensions'); + super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService); this._register( Event.debounce( Event.any( @@ -69,12 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse () => undefined, 500)(() => this._onDidChangeLocal.fire())); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'extensions'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableExtensions')) { @@ -154,7 +130,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply(previewResult); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -176,16 +152,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse throw new Error('Extensions: Conflicts should not occur'); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localExtensions = await this.getLocalExtensions(); @@ -202,30 +168,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.replaceQueue.queue(async () => { - const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); - const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; - const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; - const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier)); - if (removedExtensions.length) { - for (const removedExtension of removedExtensions) { - remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1); - } - this.logService.info(`Extensions: Removing extension '${identifier.id}' from remote.`); - await this.writeToRemote(remoteExtensions, remoteData.ref); - } - }); - } - - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncExtensionsResource); - } catch (e) { /* ignore */ } - } - private async getPreview(): Promise { - const lastSyncData = await this.getLastSyncUserData(); + const lastSyncData = await this.getLastSyncUserData(); const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null; const skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; @@ -262,13 +206,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remote) { // update remote this.logService.info('Extensions: Updating remote extensions...'); - remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); + const content = JSON.stringify(remote); + const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + remoteUserData = { ref, content }; } if (remoteUserData.content) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); - await this.updateLastSyncValue({ ...remoteUserData, skippedExtensions }); + await this.updateLastSyncUserData({ ...remoteUserData, skippedExtensions }); } } @@ -331,27 +277,4 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse .map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) })); } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncExtensionsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise { - await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null); - } - - private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { - const content = JSON.stringify(extensions); - ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); - return { content, ref }; - } - } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index f3275f87b1b..dd865d65b8a 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; +import { dirname } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; @@ -27,37 +26,19 @@ interface ISyncPreviewResult { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - private static EXTERNAL_USER_DATA_GLOBAL_STATE_KEY: string = 'globalState'; - - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncGlobalStateResource: URI; - constructor( @IFileService fileService: IFileService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.UIState, fileService, environmentService); - this.lastSyncGlobalStateResource = joinPath(this.syncFolder, '.lastSyncGlobalState'); + super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'globalState'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableUIState')) { @@ -132,7 +113,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace('UI State: Finished synchronizing ui state.'); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -153,16 +134,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs throw new Error('UI State: Conflicts should not occur'); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localGloablState = await this.getLocalGlobalState(); @@ -179,12 +150,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return null; } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncGlobalStateResource); - } catch (e) { /* ignore */ } - } - private async getPreview(): Promise { const lastSyncData = await this.getLastSyncUserData(); const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null; @@ -209,13 +174,15 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (remote) { // update remote this.logService.info('UI State: Updating remote ui state...'); - remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); + const content = JSON.stringify(remote); + const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + remoteUserData = { ref, content }; } if (remoteUserData.content) { // update last sync this.logService.info('UI State: Updating last synchronised ui state...'); - await this.updateLastSyncValue(remoteUserData); + await this.updateLastSyncUserData(remoteUserData); } } @@ -245,27 +212,4 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncGlobalStateResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncGlobalStateResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null); - } - - private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise { - const content = JSON.stringify(globalState); - ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref); - return { content, ref }; - } - } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index d4841150c24..dc75dba9008 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,23 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; -import { Emitter, Event } from 'vs/base/common/event'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncContent { mac?: string; @@ -37,42 +34,22 @@ interface ISyncPreviewResult { readonly hasConflicts: boolean; } -export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - - private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; +export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements IUserDataSynchroniser { private syncPreviewResultPromise: CancelablePromise | null = null; - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncKeybindingsResource: URI; - constructor( - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, ) { - super(SyncSource.Keybindings, fileService, environmentService); - this.lastSyncKeybindingsResource = joinPath(this.syncFolder, '.lastSyncKeybindings.json'); - this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire())); + super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'keybindings'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableKeybindings')) { @@ -204,16 +181,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -243,12 +210,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs return content ? this.getKeybindingsContentFromSyncContent(content) : null; } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncKeybindingsResource); - } catch (e) { /* ignore */ } - } - private async doSync(): Promise { try { const result = await this.getPreview(); @@ -266,7 +227,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -306,7 +267,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } if (hasLocalChanged) { this.logService.info('Keybindings: Updating local keybindings'); - await this.updateLocalContent(content, fileContent); + await this.updateLocalFileContent(content, fileContent); } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); @@ -400,46 +361,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs return this._formattingOptions; } - private async getLocalFileContent(): Promise { - try { - return await this.fileService.readFile(this.environmentService.keybindingsResource); - } catch (error) { - return null; - } - } - - private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { - if (oldContent) { - // file exists already - await this.backupLocal(oldContent.value); - await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); - } else { - // file does not exist - await this.fileService.createFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), { overwrite: false }); - } - } - - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncUserData(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - - private async getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null); - } - - private async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); - } - private getKeybindingsContentFromSyncContent(syncContent: string): string | null { try { const parsed = JSON.parse(syncContent); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index fc707cbc9b5..74de5ba7397 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,15 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -22,7 +20,7 @@ import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -34,49 +32,33 @@ interface ISyncPreviewResult { readonly conflictSettings: IConflictSetting[]; } -export class SettingsSynchroniser extends AbstractSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractFileSynchroniser implements ISettingsSyncService { _serviceBrand: any; - private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings'; - private syncPreviewResultPromise: CancelablePromise | null = null; - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private _conflicts: IConflictSetting[] = []; get conflicts(): IConflictSetting[] { return this._conflicts; } private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncSettingsResource: URI; - constructor( @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.Settings, fileService, environmentService); - this.lastSyncSettingsResource = joinPath(this.syncFolder, '.lastSyncSettings.json'); - this._register(this.fileService.watch(dirname(this.environmentService.settingsResource))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire())); + super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - if (this._status !== SyncStatus.HasConflicts) { + protected getRemoteDataResourceKey(): string { return 'settings'; } + + protected setStatus(status: SyncStatus): void { + super.setStatus(status); + if (this.status !== SyncStatus.HasConflicts) { this.setConflicts([]); } } @@ -206,16 +188,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti this.setStatus(SyncStatus.Idle); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -279,12 +251,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncSettingsResource); - } catch (e) { /* ignore */ } - } - private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { try { const result = await this.getPreview(resolvedConflicts); @@ -302,7 +268,7 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -343,18 +309,18 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } if (hasLocalChanged) { this.logService.info('Settings: Updating local settings'); - await this.writeToLocal(content, fileContent); + await this.updateLocalFileContent(content, fileContent); } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); const remoteContent = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils); this.logService.info('Settings: Updating remote settings'); - const ref = await this.writeToRemote(remoteContent, forcePush ? null : remoteUserData.ref); + const ref = await this.updateRemoteUserData(remoteContent, forcePush ? null : remoteUserData.ref); remoteUserData = { ref, content }; } if (remoteUserData.content) { this.logService.info('Settings: Updating last synchronised sttings'); - await this.updateLastSyncValue(remoteUserData); + await this.updateLastSyncUserData(remoteUserData); } // Delete the preview @@ -436,46 +402,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti return this._formattingOptions; } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncSettingsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async getLocalFileContent(): Promise { - try { - return await this.fileService.readFile(this.environmentService.settingsResource); - } catch (error) { - return null; - } - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null); - } - - private async writeToRemote(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref); - } - - private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { - if (oldContent) { - // file exists already - await this.backupLocal(oldContent.value); - await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); - } else { - // file does not exist - await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); - } - } - - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncSettingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - } export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts index 6f53447afb3..d3a5a7d52ec 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -4,16 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { timeout } from 'vs/base/common/async'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncService { _serviceBrand: any; private enabled: boolean = false; + private successiveFailures: number = 0; + + 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; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -41,6 +45,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer this.sync(true, auto); return; } else { + this.successiveFailures = 0; if (stopIfDisabled) { this.userDataSyncService.stop(); this.logService.info('Auto sync stopped.'); @@ -65,11 +70,17 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer } } await this.userDataSyncService.sync(); + this.successiveFailures = 0; } catch (e) { + this.successiveFailures++; this.logService.error(e); + this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + } + if (this.successiveFailures > 5) { + this._onError.fire({ code: UserDataSyncErrorCode.TooManyFailures }); } if (loop) { - await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + await timeout(1000 * 60 * 5 * (this.successiveFailures + 1)); // Loop sync for every (successive failures count + 1) times 5 mins interval. this.sync(loop, true); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index aeb1bd8425b..ccdc00d60f1 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -123,15 +123,18 @@ export interface IUserData { content: string | null; } -export enum UserDataSyncStoreErrorCode { +export enum UserDataSyncErrorCode { + TooLarge = 'TooLarge', Unauthroized = 'Unauthroized', Rejected = 'Rejected', - Unknown = 'Unknown' + Unknown = 'Unknown', + TooManyFailures = 'TooManyFailures', + ConnectionRefused = 'ConnectionRefused' } -export class UserDataSyncStoreError extends Error { +export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncStoreErrorCode) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { super(message); } @@ -151,8 +154,8 @@ export const IUserDataSyncStoreService = createDecorator; - write(key: string, content: string, ref: string | null): Promise; + read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise; + write(key: string, content: string, ref: string | null, source?: SyncSource): Promise; clear(): Promise; } @@ -171,7 +174,7 @@ export const enum SyncSource { Settings = 'Settings', Keybindings = 'Keybindings', Extensions = 'Extensions', - UIState = 'UI State' + GlobalState = 'GlobalState' } export const enum SyncStatus { @@ -209,7 +212,6 @@ export interface IUserDataSyncService extends ISynchroniser { isFirstTimeSyncAndHasUserData(): Promise; reset(): Promise; resetLocal(): Promise; - removeExtension(identifier: IExtensionIdentifier): Promise; getRemoteContent(source: SyncSource): Promise; resolveConflictsAndContinueSync(content: string): Promise; } @@ -217,6 +219,7 @@ export interface IUserDataSyncService extends ISynchroniser { export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; + onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; triggerAutoSync(): Promise; } @@ -263,5 +266,5 @@ export function toRemoteContentResource(source: SyncSource): URI { return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` }); } export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined { - return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.UIState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; + return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index c6340f89049..4519914e70c 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -30,7 +30,6 @@ export class UserDataSyncChannel implements IServerChannel { case 'push': return this.service.push(); case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); - case 'removeExtension': return this.service.removeExtension(args[0]); case 'stop': this.service.stop(); return Promise.resolve(); case 'restart': return this.service.restart().then(() => this.service.status); case 'reset': return this.service.reset(); @@ -84,6 +83,9 @@ export class UserDataAutoSyncChannel implements IServerChannel { constructor(private readonly service: IUserDataAutoSyncService) { } listen(_: unknown, event: string): Event { + switch (event) { + case 'onError': return this.service.onError; + } throw new Error(`Event not found: ${event}`); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 270bab9d5a2..141533aa02f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -9,11 +9,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; -import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { localize } from 'vs/nls'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +type SyncConflictsClassification = { + source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -41,6 +45,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); @@ -247,18 +252,21 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.info('Completed resetting local cache'); } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.extensionsSynchroniser.removeExtension(identifier); - } - private updateStatus(): void { - this._conflictsSource = this.computeConflictsSource(); - this.setStatus(this.computeStatus()); - } - - private setStatus(status: SyncStatus): void { + const status = this.computeStatus(); if (this._status !== status) { + const oldStatus = this._status; + const oldConflictsSource = this._conflictsSource; + this._conflictsSource = this.computeConflictsSource(); this._status = status; + if (status === SyncStatus.HasConflicts) { + // Log to telemetry when there is a sync conflict + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this._conflictsSource! }); + } + if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { + // Log to telemetry when conflicts are resolved + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: oldConflictsSource! }); + } this._onDidChangeStatus.fire(status); } } @@ -296,7 +304,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (synchroniser instanceof ExtensionsSynchroniser) { return SyncSource.Extensions; } - return SyncSource.UIState; + return SyncSource.GlobalState; } private onDidChangeAuthTokenStatus(token: string | undefined): void { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index f33c1880600..a5e77ab721e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; @@ -27,7 +27,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(configurationService); } - async read(key: string, oldValue: IUserData | null): Promise { + async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -40,7 +40,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -59,7 +59,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return { ref, content }; } - async write(key: string, data: string, ref: string | null): Promise { + async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -70,12 +70,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None); - - if (context.res.statusCode === 412) { - // There is a new value. Throw Rejected Error - throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected); - } + const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); @@ -96,14 +91,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; - const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); } } - private async request(options: IRequestOptions, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new Error('No Auth Token Available.'); @@ -111,15 +106,30 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn options.headers = options.headers || {}; options.headers['authorization'] = `Bearer ${authToken}`; - const context = await this.requestService.request(options, token); + let context; + + try { + context = await this.requestService.request(options, token); + } catch (e) { + throw new UserDataSyncError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source); + } if (context.res.statusCode === 401) { // Throw Unauthorized Error - throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); + throw new UserDataSyncError(`Request '${options.url?.toString()}' is not authorized.`, UserDataSyncErrorCode.Unauthroized, source); + } + + if (context.res.statusCode === 412) { + // There is a new value. Throw Rejected Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed with precondition. There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source); + } + + if (context.res.statusCode === 413) { + // Throw Too Large Payload Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed because data is too large.`, UserDataSyncErrorCode.TooLarge, source); } return context; - } } diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 2e8d2a42372..bbe7afd722e 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -1201,7 +1201,7 @@ suite('SettingsMerge - Add Setting', () => { assert.equal(actual, expected); }); - test('Insert before a setting and before a comment at the begining', () => { + test('Insert before a setting and before a comment at the beginning', () => { const sourceContent = ` { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 4dbaf176ef2..ca87b9be80c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -13,7 +13,7 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { ipcMain as ipc, screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron'; +import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -226,13 +226,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // React to HC color scheme changes (Windows) if (isWindows) { - nativeTheme.on('updated', () => { - if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { + const onHighContrastChange = () => { + if (systemPreferences.isInvertedColorScheme() || systemPreferences.isHighContrastColorScheme()) { this.sendToAll('vscode:enterHighContrast'); } else { this.sendToAll('vscode:leaveHighContrast'); } - }); + }; + + systemPreferences.on('inverted-color-scheme-changed', () => onHighContrastChange()); + systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); } // When a window looses focus, save all windows state. This allows to diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index aead2a92514..da52741de87 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -796,12 +796,12 @@ declare module 'vscode' { */ export class ThemeIcon { /** - * Reference to a icon representing a file. The icon is taken from the current file icon theme or a placeholder icon. + * Reference to an icon representing a file. The icon is taken from the current file icon theme or a placeholder icon is used. */ static readonly File: ThemeIcon; /** - * Reference to a icon representing a folder. The icon is taken from the current file icon theme or a placeholder icon. + * Reference to an icon representing a folder. The icon is taken from the current file icon theme or a placeholder icon is used. */ static readonly Folder: ThemeIcon; @@ -3983,7 +3983,7 @@ declare module 'vscode' { /** * The range at which this item is called. This is the range relative to the caller, e.g the item - * passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls) + * passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyProvider.provideCallHierarchyOutgoingCalls) * and not [`this.to`](#CallHierarchyOutgoingCall.to). */ fromRanges: Range[]; @@ -7602,7 +7602,7 @@ declare module 'vscode' { onDidWrite: Event; /** - * An event that when fired allows overriding the [dimensions](#Terminal.dimensions) of the + * An event that when fired allows overriding the [dimensions](#Pseudoterminal.setDimensions) of the * terminal. Note that when set, the overridden dimensions will only take effect when they * are lower than the actual dimensions of the terminal (ie. there will never be a scroll * bar). Set to `undefined` for the terminal to go back to the regular dimensions (fit to diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 607cc0bcd83..ff9d5389785 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -22,6 +22,7 @@ declare module 'vscode' { id: string; accessToken: string; displayName: string; + scopes: string[] } export interface AuthenticationProvider { @@ -37,7 +38,7 @@ declare module 'vscode' { /** * Prompts a user to login. */ - login(): Promise; + login(scopes: string[]): Promise; logout(sessionId: string): Promise; } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 979b50d5764..d2d98d8987b 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -24,8 +24,8 @@ export class MainThreadAuthenticationProvider { return this._proxy.$getSessions(this.id); } - login(): Promise { - return this._proxy.$login(this.id); + login(scopes: string[]): Promise { + return this._proxy.$login(this.id, scopes); } logout(accountId: string): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 96eb4dee60e..68454e67aca 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -160,7 +160,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { // --- from extension host process $trySaveDocument(uri: UriComponents): Promise { - return this._textFileService.save(URI.revive(uri)); + return this._textFileService.save(URI.revive(uri)).then(target => !!target); } $tryOpenDocument(_uri: UriComponents): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index 78f8439bf22..30e3d7bca23 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -8,6 +8,7 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; interface QuickInputSession { input: IQuickInput; @@ -185,14 +186,22 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { return this._quickInputService.backButton; } const { iconPath, tooltip, handle } = button; - return { - iconPath: iconPath && { - dark: URI.revive(iconPath.dark), - light: iconPath.light && URI.revive(iconPath.light) - }, - tooltip, - handle - }; + if ('id' in iconPath) { + return { + iconClass: ThemeIcon.asClassName(iconPath), + tooltip, + handle + }; + } else { + return { + iconPath: { + dark: URI.revive(iconPath.dark), + light: iconPath.light && URI.revive(iconPath.light) + }, + tooltip, + handle + }; + } }); } else { (input as any)[param] = params[param]; diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e88f2c3df7c..635ac83eb8b 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -23,14 +23,7 @@ import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote. import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -313,28 +306,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - - class CustomViewPaneContainer extends ViewPaneContainer { - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - } - viewContainer = this.viewContainersRegistry.registerViewContainer({ id, name: title, extensionId, - ctorDescriptor: new SyncDescriptor(CustomViewPaneContainer), + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }] + ), hideIfEmpty: true, icon, }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1a107144755..045e8ce561c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -439,8 +439,10 @@ export interface TransferQuickPickItems extends quickInput.IQuickPickItem { handle: number; } -export interface TransferQuickInputButton extends quickInput.IQuickInputButton { +export interface TransferQuickInputButton { handle: number; + iconPath: { dark: URI; light?: URI; } | { id: string; }; + tooltip?: string; } export type TransferQuickInput = TransferQuickPick | TransferInputBox; @@ -908,8 +910,8 @@ export interface ExtHostLabelServiceShape { export interface ExtHostAuthenticationShape { $getSessions(id: string): Promise>; - $login(id: string): Promise; - $logout(id: string, accountId: string): Promise; + $login(id: string, scopes: string[]): Promise; + $logout(id: string, sessionId: string): Promise; } export interface ExtHostSearchShape { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index c9cedd3af3d..c4518b0a407 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -37,13 +37,13 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi return this._provider.getSessions(); } - async login(): Promise { + async login(scopes: string[]): Promise { const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); if (!isAllowed) { throw new Error('User did not consent to login.'); } - return this._provider.login(); + return this._provider.login(scopes); } logout(sessionId: string): Promise { @@ -93,10 +93,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - $login(providerId: string): Promise { + $login(providerId: string, scopes: string[]): Promise { const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { - return Promise.resolve(authProvider.login()); + return Promise.resolve(authProvider.login(scopes)); } throw new Error(`Unable to find authentication provider with handle: ${0}`); diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index aa41e81b660..fa1aca30a20 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -443,31 +443,21 @@ class ExtHostQuickInput implements QuickInput { } } -function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | undefined { - const dark = getDarkIconUri(iconPath); - const light = getLightIconUri(iconPath); - if (!light && !dark) { - return undefined; +function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | { id: string } { + if (iconPath instanceof ThemeIcon) { + return { id: iconPath.id }; } - return { dark: (dark || light)!, light }; + const dark = getDarkIconUri(iconPath as any); + const light = getLightIconUri(iconPath as any); + return { dark, light }; } -function getLightIconUri(iconPath: QuickInputButton['iconPath']) { - if (iconPath && !(iconPath instanceof ThemeIcon)) { - if (typeof iconPath === 'string' - || URI.isUri(iconPath)) { - return getIconUri(iconPath); - } - return getIconUri((iconPath as any).light); - } - return undefined; +function getLightIconUri(iconPath: string | URI | { light: URI; dark: URI; }) { + return getIconUri(typeof iconPath === 'object' && 'light' in iconPath ? iconPath.light : iconPath); } -function getDarkIconUri(iconPath: QuickInputButton['iconPath']) { - if (iconPath && !(iconPath instanceof ThemeIcon) && (iconPath as any).dark) { - return getIconUri((iconPath as any).dark); - } - return undefined; +function getDarkIconUri(iconPath: string | URI | { light: URI; dark: URI; }) { + return getIconUri(typeof iconPath === 'object' && 'dark' in iconPath ? iconPath.dark : iconPath); } function getIconUri(iconPath: string | URI) { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 4f893d318d4..0e441ee01d5 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -304,7 +304,7 @@ namespace schema { type: 'string' }, icon: { - description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'), + description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `$(zap)`'), anyOf: [{ type: 'string' }, diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index cf9666dc65d..d4784a72e85 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -25,6 +25,8 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { clamp } from 'vs/base/common/numbers'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; class InspectContextKeysAction extends Action { @@ -210,6 +212,33 @@ class LogStorageAction extends Action { } } +class LogWorkingCopiesAction extends Action { + + static readonly ID = 'workbench.action.logWorkingCopies'; + static readonly LABEL = nls.localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"); + + constructor( + id: string, + label: string, + @ILogService private logService: ILogService, + @IWorkingCopyService private workingCopyService: IWorkingCopyService + ) { + super(id, label); + } + + async run(): Promise { + const msg = [ + `Dirty Working Copies:`, + ...this.workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)), + ``, + `All Working Copies:`, + ...this.workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)), + ]; + + this.logService.info(msg.join('\n')); + } +} + // --- Actions Registration const developerCategory = nls.localize('developer', "Developer"); @@ -217,6 +246,7 @@ const registry = Registry.as(Extensions.WorkbenchActio registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(LogWorkingCopiesAction, LogWorkingCopiesAction.ID, LogWorkingCopiesAction.LABEL), 'Developer: Log Working Copies', developerCategory); // Screencast Mode const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 5fab49f84df..325714a925e 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -151,8 +151,8 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array; - tree.setInput(model); const textModel = model.textModel; const overrideConfiguration = { @@ -481,6 +480,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { }; this._outlineComparator.type = this._getOutlineItemCompareType(overrideConfiguration); + tree.setInput(model); if (element !== model) { tree.reveal(element, 0.5); tree.setFocus([element], this._fakeEvent); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 310c9d2e9b3..af12e72ab46 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -30,13 +30,13 @@ import { NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, - OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, + OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup, ShowEditorsInActiveGroupByMostRecentlyUsedAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenNextRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -426,12 +426,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRows registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); // Register Quick Editor Actions including built in quick navigate support for some -const quickOpenNextRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; -const quickOpenPreviousRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorAction.ID, QuickOpenNextRecentlyUsedEditorAction.LABEL), 'View: Quick Open Next Recently Used Editor', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorInGroupAction.ID, QuickOpenNextRecentlyUsedEditorInGroupAction.LABEL, quickOpenNextRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Next Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, quickOpenPreviousRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; @@ -440,8 +436,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 50, handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true), when: editorPickerContext, - primary: quickOpenNextRecentlyUsedEditorInGroupKeybinding.primary, - mac: quickOpenNextRecentlyUsedEditorInGroupKeybinding.mac + primary: KeyMod.CtrlCmd | KeyCode.Tab, + mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }); const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; @@ -450,8 +446,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 50, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false), when: editorPickerContext, - primary: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.primary, - mac: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.mac + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }); // Editor Commands diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 621adcab2cd..9f3f7a663bb 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -543,13 +543,13 @@ export class RevertAndCloseEditorAction extends Action { // first try a normal revert where the contents of the editor are restored try { - await editor.revert(); + await this.editorService.revert({ editor, groupId: group.id }); } catch (error) { // if that fails, since we are about to close the editor, we accept that // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - await editor.revert({ soft: true }); + await this.editorService.revert({ editor, groupId: group.id }, { soft: true }); } group.closeEditor(editor); @@ -1337,21 +1337,6 @@ export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEdit } } -export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { - - static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditor'; - static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditor', "Quick Open Next Recently Used Editor"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); - } -} - export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; @@ -1367,21 +1352,6 @@ export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickO } } -export class QuickOpenNextRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { - - static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditorInGroup', "Quick Open Next Recently Used Editor in Group"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); - } -} - export class QuickOpenPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6de26a8919b..91f83409e66 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, SaveContext, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -464,11 +464,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private registerListeners(): void { // Model Events - this._register(this._group.onDidEditorPin(editor => this.onDidEditorPin(editor))); - this._register(this._group.onDidEditorOpen(editor => this.onDidEditorOpen(editor))); - this._register(this._group.onDidEditorClose(editor => this.onDidEditorClose(editor))); - this._register(this._group.onDidEditorDispose(editor => this.onDidEditorDispose(editor))); - this._register(this._group.onDidEditorBecomeDirty(editor => this.onDidEditorBecomeDirty(editor))); + this._register(this._group.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor))); + this._register(this._group.onDidOpenEditor(editor => this.onDidOpenEditor(editor))); + this._register(this._group.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor))); + this._register(this._group.onDidDisposeEditor(editor => this.onDidDisposeEditor(editor))); + this._register(this._group.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor))); this._register(this._group.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor))); // Option Changes @@ -478,13 +478,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._register(this.accessor.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); } - private onDidEditorPin(editor: EditorInput): void { + private onDidChangeEditorPinned(editor: EditorInput): void { // Event this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_PIN, editor }); } - private onDidEditorOpen(editor: EditorInput): void { + private onDidOpenEditor(editor: EditorInput): void { /* __GDPR__ "editorOpened" : { @@ -502,7 +502,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_OPEN, editor }); } - private onDidEditorClose(event: EditorCloseEvent): void { + private handleOnDidCloseEditor(event: EditorCloseEvent): void { // Before close this._onWillCloseEditor.fire(event); @@ -559,7 +559,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return descriptor; } - private onDidEditorDispose(editor: EditorInput): void { + private onDidDisposeEditor(editor: EditorInput): void { // To prevent race conditions, we handle disposed editors in our worker with a timeout // because it can happen that an input is being disposed with the intent to replace @@ -625,7 +625,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } - private onDidEditorBecomeDirty(editor: EditorInput): void { + private onDidChangeEditorDirty(editor: EditorInput): void { // Always show dirty editors pinned this.pinEditor(editor); @@ -1330,7 +1330,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(this._group.id, { reason: SaveReason.EXPLICIT, context: SaveContext.EDITOR_CLOSE }); + const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT }); return !result; case ConfirmResult.DONT_SAVE: @@ -1338,7 +1338,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { try { // first try a normal revert where the contents of the editor are restored - const result = await editor.revert(); + const result = await editor.revert(this.id); return !result; } catch (error) { @@ -1346,7 +1346,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - const result = await editor.revert({ soft: true }); + const result = await editor.revert(this.id, { soft: true }); return !result; } diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 427a7bce430..ed715748812 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -151,21 +151,6 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { }; } - const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - return firstPart.shiftKey; - })); - - if (isShiftNavigate) { - return { - autoFocusLastEntry: true - }; - } - const editors = this.count(); return { autoFocusFirstEntry: editors === 1, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ab700375161..f23aacde162 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -16,7 +16,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; -import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -195,7 +195,7 @@ export class TitlebarPart extends Part implements ITitleService { // Apply listener for dirty and label changes const activeEditor = this.editorService.activeEditor; - if (activeEditor instanceof EditorInput) { + if (activeEditor) { this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 60b31ed492a..f2a68f351d3 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -133,7 +133,8 @@ import { URI } from 'vs/base/common/uri'; 'workbench.editor.mouseBackForwardToNavigate': { 'type': 'boolean', 'description': nls.localize('mouseBackForwardToNavigate', "Navigate between open files using mouse buttons four and five if provided."), - 'default': true + 'default': true, + 'included': !isMacintosh }, 'workbench.editor.restoreViewState': { 'type': 'boolean', diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index b7eabf42b67..90c2f81985d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -201,7 +201,7 @@ export interface IEditorInputFactoryRegistry { export interface IEditorInputFactory { /** - * Determines wether the given editor input can be serialized by the factory. + * Determines whether the given editor input can be serialized by the factory. */ canSerialize(editorInput: IEditorInput): boolean; @@ -299,15 +299,6 @@ export const enum SaveReason { WINDOW_CHANGE = 4 } -export const enum SaveContext { - - /** - * Indicates that the editor is saved because it - * is being closed by the user. - */ - EDITOR_CLOSE = 1, -} - export interface ISaveOptions { /** @@ -315,11 +306,6 @@ export interface ISaveOptions { */ reason?: SaveReason; - /** - * Additional information about the context of the save. - */ - context?: SaveContext; - /** * Forces to save the contents of the working copy * again even if the working copy is not dirty. @@ -362,6 +348,16 @@ export interface IEditorInput extends IDisposable { */ readonly onDispose: Event; + /** + * Triggered when this input changes its dirty state. + */ + readonly onDidChangeDirty: Event; + + /** + * Triggered when this input changes its label + */ + readonly onDidChangeLabel: Event; + /** * Returns the associated resource of this input. */ @@ -416,23 +412,31 @@ export interface IEditorInput extends IDisposable { isSaving(): boolean; /** - * Saves the editor. The provided groupId helps - * implementors to e.g. preserve view state of the editor - * and re-open it in the correct group after saving. + * Saves the editor. The provided groupId helps implementors + * to e.g. preserve view state of the editor and re-open it + * in the correct group after saving. + * + * @returns the resulting editor input (typically the same) of + * this operation or `undefined` to indicate that the operation + * failed or was canceled. */ - save(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + save(group: GroupIdentifier, options?: ISaveOptions): Promise; /** - * Saves the editor to a different location. The provided groupId + * Saves the editor to a different location. The provided `group` * helps implementors to e.g. preserve view state of the editor * and re-open it in the correct group after saving. + * + * @returns the resulting editor input (typically a different one) + * of this operation or `undefined` to indicate that the operation + * failed or was canceled. */ - saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise; /** - * Reverts this input. + * Reverts this input from the provided group. */ - revert(options?: IRevertOptions): Promise; + revert(group: GroupIdentifier, options?: IRevertOptions): Promise; /** * Returns if the other object matches this input. @@ -524,15 +528,15 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return false; } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return true; + async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return true; + async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; } - async revert(options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return true; } @@ -552,8 +556,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } dispose(): void { - this.disposed = true; - this._onDispose.fire(); + if (!this.disposed) { + this.disposed = true; + this._onDispose.fire(); + } super.dispose(); } @@ -574,43 +580,37 @@ export abstract class TextEditorInput extends EditorInput { return this.resource; } - async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.textFileService.save(this.resource, options); + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSave(group, options, false); } - saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options)); + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSave(group, options, true); } - protected async doSaveAs(group: GroupIdentifier, options: ISaveOptions | undefined, saveRunnable: () => Promise, replaceAllEditors?: boolean): Promise { + private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise { - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. - let viewState: IEditorViewState | undefined = undefined; - const editor = await this.editorService.openEditor(this, undefined, group); - if (isTextEditor(editor)) { - viewState = editor.getViewState(); + // Save / Save As + let target: URI | undefined; + if (saveAs) { + target = await this.textFileService.saveAs(this.resource, undefined, options); + } else { + target = await this.textFileService.save(this.resource, options); } - // Save as - const target = await saveRunnable(); if (!target) { - return false; // save cancelled + return undefined; // save cancelled } - // Replace editor preserving viewstate (either across all groups or - // only selected group) if the target is different from the current resource - // and if the editor is not being saved because it is being closed - // (because in that case we do not want to open a different editor anyway) - if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(target, this.resource)) { - const replacement = this.editorService.createInput({ resource: target }); - const targetGroups = replaceAllEditors ? this.editorGroupService.groups.map(group => group.id) : [group]; - for (const group of targetGroups) { - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group); - } + if (!isEqual(target, this.resource)) { + return this.editorService.createInput({ resource: target }); } - return true; + return this; + } + + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return this.textFileService.revert(this.resource, options); } } @@ -733,16 +733,16 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isSaving(); } - save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this.master.save(groupId, options); + save(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.save(group, options); } - saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this.master.saveAs(groupId, options); + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.saveAs(group, options); } - revert(options?: IRevertOptions): Promise { - return this.master.revert(options); + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return this.master.revert(group, options); } getTelemetryDescriptor(): { [key: string]: unknown } { diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index e076cfb8df5..4274f2eb876 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -17,7 +17,7 @@ export class DiffEditorInput extends SideBySideEditorInput { static readonly ID = 'workbench.editors.diffEditorInput'; - private cachedModel: DiffEditorModel | null = null; + private cachedModel: DiffEditorModel | undefined = undefined; constructor( protected name: string | undefined, @@ -96,7 +96,7 @@ export class DiffEditorInput extends SideBySideEditorInput { // them without sideeffects. if (this.cachedModel) { this.cachedModel.dispose(); - this.cachedModel = null; + this.cachedModel = undefined; } super.dispose(); diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index c304011a3a5..511a03b9376 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -57,32 +57,29 @@ export class EditorGroup extends Disposable { //#region events - private readonly _onDidEditorActivate = this._register(new Emitter()); - readonly onDidEditorActivate = this._onDidEditorActivate.event; + private readonly _onDidActivateEditor = this._register(new Emitter()); + readonly onDidActivateEditor = this._onDidActivateEditor.event; - private readonly _onDidEditorOpen = this._register(new Emitter()); - readonly onDidEditorOpen = this._onDidEditorOpen.event; + private readonly _onDidOpenEditor = this._register(new Emitter()); + readonly onDidOpenEditor = this._onDidOpenEditor.event; - private readonly _onDidEditorClose = this._register(new Emitter()); - readonly onDidEditorClose = this._onDidEditorClose.event; + private readonly _onDidCloseEditor = this._register(new Emitter()); + readonly onDidCloseEditor = this._onDidCloseEditor.event; - private readonly _onDidEditorDispose = this._register(new Emitter()); - readonly onDidEditorDispose = this._onDidEditorDispose.event; + private readonly _onDidDisposeEditor = this._register(new Emitter()); + readonly onDidDisposeEditor = this._onDidDisposeEditor.event; - private readonly _onDidEditorBecomeDirty = this._register(new Emitter()); - readonly onDidEditorBecomeDirty = this._onDidEditorBecomeDirty.event; + private readonly _onDidChangeEditorDirty = this._register(new Emitter()); + readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event; - private readonly _onDidEditorLabelChange = this._register(new Emitter()); - readonly onDidEditorLabelChange = this._onDidEditorLabelChange.event; + private readonly _onDidChangeEditorLabel = this._register(new Emitter()); + readonly onDidEditorLabelChange = this._onDidChangeEditorLabel.event; - private readonly _onDidEditorMove = this._register(new Emitter()); - readonly onDidEditorMove = this._onDidEditorMove.event; + private readonly _onDidMoveEditor = this._register(new Emitter()); + readonly onDidMoveEditor = this._onDidMoveEditor.event; - private readonly _onDidEditorPin = this._register(new Emitter()); - readonly onDidEditorPin = this._onDidEditorPin.event; - - private readonly _onDidEditorUnpin = this._register(new Emitter()); - readonly onDidEditorUnpin = this._onDidEditorUnpin.event; + private readonly _onDidChangeEditorPinned = this._register(new Emitter()); + readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event; //#endregion @@ -218,7 +215,7 @@ export class EditorGroup extends Disposable { this.registerEditorListeners(newEditor); // Event - this._onDidEditorOpen.fire(newEditor); + this._onDidOpenEditor.fire(newEditor); // Handle active if (makeActive) { @@ -257,22 +254,22 @@ export class EditorGroup extends Disposable { const onceDispose = Event.once(editor.onDispose); listeners.add(onceDispose(() => { if (this.indexOf(editor) >= 0) { - this._onDidEditorDispose.fire(editor); + this._onDidDisposeEditor.fire(editor); } })); // Re-Emit dirty state changes listeners.add(editor.onDidChangeDirty(() => { - this._onDidEditorBecomeDirty.fire(editor); + this._onDidChangeEditorDirty.fire(editor); })); // Re-Emit label changes listeners.add(editor.onDidChangeLabel(() => { - this._onDidEditorLabelChange.fire(editor); + this._onDidChangeEditorLabel.fire(editor); })); // Clean up dispose listeners once the editor gets closed - listeners.add(this.onDidEditorClose(event => { + listeners.add(this.onDidCloseEditor(event => { if (event.editor.matches(editor)) { dispose(listeners); } @@ -288,7 +285,7 @@ export class EditorGroup extends Disposable { this.splice(replaceIndex, false, replaceWith); if (event) { - this._onDidEditorClose.fire(event); + this._onDidCloseEditor.fire(event); } } @@ -296,7 +293,7 @@ export class EditorGroup extends Disposable { const event = this.doCloseEditor(candidate, openNext, false); if (event) { - this._onDidEditorClose.fire(event); + this._onDidCloseEditor.fire(event); return event.editor; } @@ -397,7 +394,7 @@ export class EditorGroup extends Disposable { this.editors.splice(toIndex, 0, editor); // Event - this._onDidEditorMove.fire(editor); + this._onDidMoveEditor.fire(editor); return editor; } @@ -426,7 +423,7 @@ export class EditorGroup extends Disposable { this.mru.unshift(editor); // Event - this._onDidEditorActivate.fire(editor); + this._onDidActivateEditor.fire(editor); } pin(candidate: EditorInput): EditorInput | undefined { @@ -449,7 +446,7 @@ export class EditorGroup extends Disposable { this.preview = null; // Event - this._onDidEditorPin.fire(editor); + this._onDidChangeEditorPinned.fire(editor); } unpin(candidate: EditorInput): EditorInput | undefined { @@ -473,7 +470,7 @@ export class EditorGroup extends Disposable { this.preview = editor; // Event - this._onDidEditorUnpin.fire(editor); + this._onDidChangeEditorPinned.fire(editor); // Close old preview editor if any if (oldPreview) { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index c97918ed39a..48abc1f5100 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,41 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, ITextEditorModel, IModeSupport, GroupIdentifier, isTextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorModel, IModeSupport, TextEditorInput } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { basename } from 'vs/base/common/resources'; -import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing * code editor model. */ -export class ResourceEditorInput extends EditorInput implements IModeSupport { +export class ResourceEditorInput extends TextEditorInput implements IModeSupport { static readonly ID: string = 'workbench.editors.resourceEditorInput'; - private cachedModel: ResourceEditorModel | null = null; - private modelReference: Promise> | null = null; + private cachedModel: ResourceEditorModel | undefined = undefined; + private modelReference: Promise> | undefined = undefined; constructor( private name: string | undefined, private description: string | undefined, - private readonly resource: URI, + resource: URI, private preferredMode: string | undefined, @ITextModelService private readonly textModelResolverService: ITextModelService, - @ITextFileService private readonly textFileService: ITextFileService, - @IEditorService private readonly editorService: IEditorService + @ITextFileService textFileService: ITextFileService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(); - - this.name = name; - this.description = description; - this.resource = resource; + super(resource, editorService, editorGroupService, textFileService); } getResource(): URI { @@ -94,7 +91,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { // Ensure the resolved model is of expected type if (!(model instanceof ResourceEditorModel)) { ref.dispose(); - this.modelReference = null; + this.modelReference = undefined; throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); } @@ -109,28 +106,6 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { return model; } - async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. - let viewState: IEditorViewState | undefined = undefined; - const editor = await this.editorService.openEditor(this, undefined, group); - if (isTextEditor(editor)) { - viewState = editor.getViewState(); - } - - // Save as - const target = await this.textFileService.saveAs(this.resource, undefined, options); - if (!target) { - return false; // save cancelled - } - - // Open the target - await this.editorService.openEditor({ resource: target, options: { viewState, pinned: true } }, group); - - return true; - } - matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; @@ -147,10 +122,10 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { dispose(): void { if (this.modelReference) { this.modelReference.then(ref => ref.dispose()); - this.modelReference = null; + this.modelReference = undefined; } - this.cachedModel = null; + this.cachedModel = undefined; super.dispose(); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 1ef371be66f..5a3f8894c15 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -4,20 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { suggestFilename } from 'vs/base/common/mime'; import { createMemoizer } from 'vs/base/common/decorators'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { basenameOrAuthority, dirname, toLocalResource } from 'vs/base/common/resources'; -import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; +import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; +import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput } from 'vs/workbench/common/editor'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; -import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** * An editor input to be used for untitled text buffers. @@ -31,9 +28,9 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin private readonly _onDidModelChangeEncoding = this._register(new Emitter()); readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event; - private cachedModel: UntitledTextEditorModel | null = null; + private cachedModel: UntitledTextEditorModel | undefined = undefined; - private modelResolve: Promise | null = null; + private modelResolve: Promise | undefined = undefined; private preferredMode: string | undefined; @@ -47,8 +44,7 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin @ITextFileService textFileService: ITextFileService, @ILabelService private readonly labelService: ILabelService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super(resource, editorService, editorGroupService, textFileService); @@ -170,52 +166,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return this.hasAssociatedFilePath; } - save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, async () => { - - // With associated file path, save to the path that is - // associated. Make sure to convert the result using - // remote authority properly. - if (this.hasAssociatedFilePath) { - if (await this.textFileService.save(this.resource, options)) { - return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority); - } - - return; - } - - // Without associated file path, do a normal "Save As" - return this.textFileService.saveAs(this.resource, undefined, options); - }, true /* replace editor across all groups */); - } - - saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); - } - - async revert(options?: IRevertOptions): Promise { - if (this.cachedModel) { - this.cachedModel.revert(); - } - - this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it - - return true; - } - - suggestFileName(): string { - if (!this.hasAssociatedFilePath) { - if (this.cachedModel) { - const mode = this.cachedModel.getMode(); - if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text - return suggestFilename(mode, this.getName()); - } - } - } - - return this.getName(); - } - getEncoding(): string | undefined { if (this.cachedModel) { return this.cachedModel.getEncoding(); @@ -286,6 +236,9 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._register(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire())); this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire())); + + // a disposed untitled text editor model renders this input disposed + this._register(model.onDispose(() => this.dispose())); } matches(otherInput: unknown): boolean { @@ -302,8 +255,8 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin } dispose(): void { - this.cachedModel = null; - this.modelResolve = null; + this.cachedModel = undefined; + this.modelResolve = undefined; super.dispose(); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index b7773dd818c..b27ebf1b85a 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -123,6 +123,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } } + isReadonly(): boolean { + return false; + } + isDirty(): boolean { return this.dirty; } @@ -136,13 +140,20 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeDirty.fire(); } - save(options?: ISaveOptions): Promise { - return this.textFileService.save(this.resource, options); + async save(options?: ISaveOptions): Promise { + const target = await this.textFileService.save(this.resource, options); + + return !!target; } async revert(): Promise { this.setDirty(false); + // A reverted untitled model is invalid because it has + // no actual source on disk to revert to. As such we + // dispose the model. + this.dispose(); + return true; } @@ -250,8 +261,4 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeName.fire(); } } - - isReadonly(): boolean { - return false; - } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 2c2f237b11b..f5317a3d938 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,6 +26,8 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; class InspectEditorTokensController extends Disposable implements IEditorContribution { @@ -121,7 +123,8 @@ interface ISemanticTokenInfo { type: string; modifiers: string[]; range: Range; - metadata: IDecodedMetadata + metadata: IDecodedMetadata, + definitions: TokenStyleDefinitions } interface IDecodedMetadata { @@ -248,17 +251,21 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position); let tokenText; + let metadata: IDecodedMetadata | undefined; - let primary: IDecodedMetadata | undefined; - if (textMateTokenInfo) { + let tmFallback: IDecodedMetadata | undefined; + + if (semanticTokenInfo) { + tokenText = this._model.getValueInRange(semanticTokenInfo.range); + metadata = semanticTokenInfo.metadata; + if (textMateTokenInfo) { + tmFallback = textMateTokenInfo.metadata; + } + } else if (textMateTokenInfo) { let tokenStartIndex = textMateTokenInfo.token.startIndex; let tokenEndIndex = textMateTokenInfo.token.endIndex; tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex); metadata = textMateTokenInfo.metadata; - primary = semanticTokenInfo?.metadata; - } else if (semanticTokenInfo) { - tokenText = this._model.getValueInRange(semanticTokenInfo.range); - metadata = semanticTokenInfo.metadata; } else { return 'No grammar or semantic tokens available.'; } @@ -268,28 +275,36 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { result += ``; result += ``; - result += ``; - result += ``; - if (semanticTokenInfo) { - result += ``; - const modifiers = semanticTokenInfo.modifiers.join(' ') || '-'; - result += ``; - } + result += ``; + result += ``; result += ``; result += ``; result += ``; - result += this._formatMetadata(metadata, primary); + result += this._formatMetadata(metadata, tmFallback); result += ``; + if (semanticTokenInfo) { + result += ``; + result += ``; + result += ``; + const modifiers = semanticTokenInfo.modifiers.join(' ') || '-'; + result += ``; + result += ``; + + result += `
${this._renderTokenStyleDefinition(semanticTokenInfo.definitions.foreground)}
`; + } + if (textMateTokenInfo) { let theme = this._themeService.getColorTheme(); result += ``; - let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); - if (matchingRule) { - result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } else { - result += `No theme selector.`; + if (!semanticTokenInfo) { + let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); + if (matchingRule) { + result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; + } else { + result += `No theme selector.`; + } } result += `
    `; @@ -301,15 +316,20 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return result; } - private _formatMetadata(metadata: IDecodedMetadata, master?: IDecodedMetadata) { + private _formatMetadata(metadata?: IDecodedMetadata, fallback?: IDecodedMetadata) { let result = ''; - const fontStyle = master ? master.fontStyle : metadata.fontStyle; - result += `font style${fontStyle}`; - const foreground = master && master.foreground || metadata.foreground; - result += `foreground${foreground}`; - const background = master && master.background || metadata.background; - result += `background${background}`; + function render(label: string, value: string | undefined, property: keyof IDecodedMetadata) { + const info = metadata?.[property] !== value ? ` (tm)` : ''; + return `${label}${value + info}`; + } + + const fontStyle = metadata?.fontStyle || fallback?.fontStyle; + result += render('font style', fontStyle, 'fontStyle'); + const foreground = metadata?.foreground || fallback?.foreground; + result += render('foreground', foreground, 'foreground'); + const background = metadata?.background || fallback?.background; + result += render('background', background, 'background'); if (foreground && background) { const backgroundColor = Color.fromHex(background), foregroundColor = Color.fromHex(foreground); @@ -436,8 +456,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const type = semanticTokens.legend.tokenTypes[typeIdx]; const modifiers = semanticTokens.legend.tokenModifiers.filter((_, k) => modSet & 1 << k); const range = new Range(line + 1, character + 1, line + 1, character + 1 + len); - const metadata = this._decodeMetadata(this._themeService.getTheme().getTokenStyleMetadata(type, modifiers) || 0); - return { type, modifiers, range, metadata }; + const definitions = {}; + const theme = this._themeService.getTheme() as ColorThemeData; + const m = theme.getTokenStyleMetadata(type, modifiers, true, definitions); + const metadata = this._decodeMetadata(m || 0); + return { type, modifiers, range, metadata, definitions }; } lastLine = line; lastCharacter = character; @@ -445,6 +468,46 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return null; } + private _renderTokenStyleDefinition(definition: TokenStyleDefinition | undefined): string { + if (definition === undefined) { + return ''; + } + const theme = this._themeService.getTheme() as ColorThemeData; + + const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; + if (Array.isArray(definition)) { + let result = ''; + result += `
      `; + for (const d of definition) { + result += `
    • ${escape(d.join(' '))}
    • `; + } + result += `
    `; + + for (const d of definition) { + let matchingRule = findMatchingThemeRule(theme, d, false); + if (matchingRule) { + result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; + break; + } + } + return result; + } else if (isTokenStylingRule(definition)) { + + const scope = theme.getTokenStylingRuleScope(definition); + + if (scope === 'setting') { + return `User settings: ${definition.selector}`; + } else if (scope === 'theme') { + return `Color theme: ${definition.selector}`; + } + return ''; + } else if (typeof definition === 'string') { + return `Selector: ${definition}`; + } else { + return `Token style: Foreground: ${definition.foreground}, bold: ${definition.bold}, italic: ${definition.italic}, underline: ${definition.underline},`; + } + } + public getDomNode(): HTMLElement { return this._domNode; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index b8cb60b0b62..57b85f7a094 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -14,13 +14,12 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, SaveContext, Verbosity } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { WebviewEditorOverlay, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @@ -39,9 +38,9 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @ICustomEditorService private readonly customEditorService: ICustomEditorService, - @IEditorService private readonly editorService: IEditorService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IEditorService private readonly editorService: IEditorService ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; @@ -122,34 +121,38 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return false; } - public save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this._model ? this._model.save(options) : Promise.resolve(false); + public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + if (!this._model) { + return undefined; + } + + const result = await this._model.save(options); + if (!result) { + return undefined; + } + + return this; } - public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { if (!this._model) { - return false; + return undefined; } let dialogPath = this._editorResource; - const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); + const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { - return false; // save cancelled + return undefined; // save cancelled } if (!await this._model.saveAs(this._editorResource, target, options)) { - return false; + return undefined; } - if (options?.context !== SaveContext.EDITOR_CLOSE) { - const replacement = this.handleMove(groupId, target) || this.instantiationService.createInstance(FileEditorInput, target, undefined, undefined); - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], groupId); - } - - return true; + return this.handleMove(groupId, target) || this.editorService.createInput({ resource: target, forceFile: true }); } - public revert(options?: IRevertOptions): Promise { + public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this._model ? this._model.revert(options) : Promise.resolve(false); } @@ -162,14 +165,6 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return await super.resolve(); } - private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); - } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 2049d25b6ba..ba8cff7a7f6 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -15,23 +16,22 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; -import { Emitter } from 'vs/base/common/event'; -import { ILabelService } from 'vs/platform/label/common/label'; export const defaultEditorId = 'default'; @@ -204,7 +204,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ group?: IEditorGroup, ): Promise { if (viewType === defaultEditorId) { - const fileInput = this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); + const fileInput = this.editorService.createInput({ resource, forceFile: true }); return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); } @@ -221,9 +221,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string; }, - ): EditorInput { + ): IEditorInput { if (viewType === defaultEditorId) { - return this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); + return this.editorService.createInput({ resource, forceFile: true }); } const id = generateUuid(); @@ -314,12 +314,26 @@ export const customEditorsAssociationsKey = 'workbench.experimental.editorAssoci export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[]; -export class CustomEditorContribution implements IWorkbenchContribution { +export class CustomEditorContribution extends Disposable implements IWorkbenchContribution { constructor( - @IEditorService private readonly editorService: IEditorService, + @IEditorService private readonly editorService: EditorServiceImpl, @ICustomEditorService private readonly customEditorService: ICustomEditorService, ) { - this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group)); + super(); + + this._register(this.editorService.overrideOpenEditor((editor, options, group) => { + return this.onEditorOpening(editor, options, group); + })); + + this._register(this.editorService.onDidCloseEditor(({ editor }) => { + if (!(editor instanceof CustomFileEditorInput)) { + return; + } + + if (!this.editorService.editors.some(other => other === editor)) { + editor.dispose(); + } + })); } private onEditorOpening( @@ -329,7 +343,15 @@ export class CustomEditorContribution implements IWorkbenchContribution { ): IOpenEditorOverride | undefined { if (editor instanceof CustomFileEditorInput) { if (editor.group === group.id) { + // No need to do anything return undefined; + } else { + // Create a copy of the input. + // Unlike normal editor inputs, we do not want to share custom editor inputs + // between multiple editors / groups. + return { + override: this.customEditorService.openWith(editor.getResource(), editor.viewType, options, group) + }; } } @@ -435,7 +457,12 @@ export class CustomEditorContribution implements IWorkbenchContribution { return undefined; } - return this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); + const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); + if (input instanceof EditorInput) { + return input; + } + + return undefined; }; const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput, 'modified'); diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 1b1aa52381b..e7b613ef6fc 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IEditor, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { IEditor, IRevertOptions, ISaveOptions, IEditorInput } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -37,7 +37,7 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; - createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): EditorInput; + createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput; openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f5fe47a7c09..2ec7478dfa0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -252,6 +252,7 @@ CommandsRegistry.registerCommand({ } } catch (e) { onUnexpectedError(e); + throw e; } } }); @@ -284,6 +285,7 @@ CommandsRegistry.registerCommand({ await extensionManagementService.uninstall(extensionToUninstall, true); } catch (e) { onUnexpectedError(e); + throw e; } } }); diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index e4656bacabc..372492b034c 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { createMemoizer } from 'vs/base/common/decorators'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput, IRevertOptions } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; @@ -262,10 +262,6 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput return false; } - revert(options?: IRevertOptions): Promise { - return this.textFileService.revert(this.resource, options); - } - getPreferredEditorId(candidates: string[]): string { return this.forceOpenAs === ForceOpenAs.Binary ? BINARY_FILE_EDITOR_ID : TEXT_FILE_EDITOR_ID; } diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index d1a86fceb9a..4a059d873ec 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -152,7 +152,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - assert.ok(await input.revert()); + assert.ok(await input.revert(0)); assert.ok(!input.isDirty()); input.dispose(); diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 33c74fdae1f..fab74175772 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -28,9 +28,10 @@ export class LogViewerInput extends ResourceEditorInput { private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService); + super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService, editorGroupService); } getTypeId(): string { diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 31ab953bff3..bb5033348d3 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -23,6 +23,7 @@ import { mergeSort } from 'vs/base/common/arrays'; import product from 'vs/platform/product/common/product'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export class PerfviewContrib { @@ -48,7 +49,8 @@ export class PerfviewInput extends ResourceEditorInput { constructor( @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super( localize('name', "Startup Performance"), @@ -57,7 +59,8 @@ export class PerfviewInput extends ResourceEditorInput { undefined, textModelResolverService, textFileService, - editorService + editorService, + editorGroupService ); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 87b7abe01b3..60bcbfd4ba4 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -252,7 +252,7 @@ export class PreferencesEditor extends BaseEditor { if (this.editorService.activeControl !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.input.save(this.group!.id) : Promise.resolve(true); + const promise: Promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 3f01dba4e1d..54d2cfe03a6 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -90,7 +90,7 @@ class SymbolEntry extends EditorQuickOpenEntry { run(mode: Mode, context: IEntryRunContext): boolean { - // resolve this type bearing if neccessary + // resolve this type bearing if necessary if (!this.bearingResolve && typeof this.provider.resolveWorkspaceSymbol === 'function' && !this.bearing.location.range) { this.bearingResolve = Promise.resolve(this.provider.resolveWorkspaceSymbol(this.bearing, CancellationToken.None)).then(result => { this.bearing = result || this.bearing; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index bbea9b6271b..515a6dc0fe6 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -38,7 +38,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand, ReRunSearchEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -641,6 +641,12 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeA registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), + 'Search Editor: Rerun search', category, ContextKeyExpr.and(Constants.InSearchEditor) +); + registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 06abc43baf2..85cde05c712 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -574,6 +574,26 @@ export class OpenResultsInEditorAction extends Action { } +export class ReRunSearchEditorSearchAction extends Action { + + static readonly ID = 'searchEditor.rerunSerach'; + static readonly LABEL = nls.localize('search.rerunSearch', "Rerun Search in Editor"); + + constructor(id: string, label: string, + @IEditorService private readonly editorService: IEditorService) { + super(id, label); + } + + async run() { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + await (this.editorService.activeControl as SearchEditor).runSearch(); + } + } +} + + + export class FocusNextSearchResultAction extends Action { static readonly ID = 'search.action.focusNextSearchResult'; static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index f32a5af1a4e..360bda0be25 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -169,7 +169,7 @@ export class SearchEditor extends BaseEditor { } }); - + this._register(this.onDidBlur(() => this.saveViewState())); this._register(this.searchResultEditor.onKeyDown(e => e.keyCode === KeyCode.Escape && this.queryEditorWidget.searchInput.focus())); @@ -183,6 +183,10 @@ export class SearchEditor extends BaseEditor { }); } + focus() { + this.restoreViewState(); + } + focusNextInput() { if (this.queryEditorWidget.searchInputHasFocus()) { if (this.showingIncludesExcludes) { @@ -230,7 +234,7 @@ export class SearchEditor extends BaseEditor { this.queryEditorWidget.toggleContextLines(); } - private async runSearch(instant = false) { + async runSearch(instant = false) { if (!this.pauseSearching) { this.runSearchDelayer.trigger(() => this.doRunSearch(), instant ? 0 : undefined); } @@ -334,10 +338,6 @@ export class SearchEditor extends BaseEditor { this.reLayout(); } - focusInput() { - this.queryEditorWidget.focus(); - } - getSelected() { const selection = this.searchResultEditor.getSelection(); if (selection) { @@ -360,6 +360,8 @@ export class SearchEditor extends BaseEditor { } async setInput(newInput: SearchEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + this.saveViewState(); + await super.setInput(newInput, options, token); this.inSearchEditorContextKey.set(true); @@ -379,7 +381,7 @@ export class SearchEditor extends BaseEditor { this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(query.useIgnores); this.toggleIncludesExcludes(query.showIncludesExcludes); - this.focusInput(); + this.restoreViewState(); this.pauseSearching = false; } @@ -404,7 +406,32 @@ export class SearchEditor extends BaseEditor { return this.searchResultEditor.getModel(); } + private saveViewState() { + const input = this.getInput(); + if (!input) { return; } + + if (this.searchResultEditor.hasWidgetFocus()) { + const viewState = this.searchResultEditor.saveViewState(); + if (viewState) { + input.viewState = { focused: 'editor', state: viewState }; + } + } else { + input.viewState = { focused: 'input' }; + } + } + + private restoreViewState() { + const input = this.getInput(); + if (input && input.viewState && input.viewState.focused === 'editor') { + this.searchResultEditor.restoreViewState(input.viewState.state); + this.searchResultEditor.focus(); + } else { + this.queryEditorWidget.focus(); + } + } + clearInput() { + this.saveViewState(); super.clearInput(); this.inSearchEditorContextKey.set(false); } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 34faa8107c5..035ad1164e5 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -11,15 +11,14 @@ import { ITextModel, ITextBufferFactory } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorInputFactory, GroupIdentifier, EditorInput, SaveContext, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, GroupIdentifier, EditorInput, IRevertOptions, IEditorInput } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { dirname, joinPath, isEqual } from 'vs/base/common/resources'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { joinPath, isEqual } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { basename } from 'vs/base/common/path'; @@ -27,6 +26,7 @@ import { IWorkingCopyService, WorkingCopyCapabilities, IWorkingCopy, IWorkingCop import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { assertIsDefined } from 'vs/base/common/types'; import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; +import type { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; export type SearchConfiguration = { query: string, @@ -40,12 +40,17 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +type SearchEditorViewState = + | { focused: 'input' } + | { focused: 'editor', state: ICodeEditorViewState }; + export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; private dirty: boolean = false; private readonly model: Promise; private resolvedModel?: { model: ITextModel, query: SearchConfiguration }; + viewState: SearchEditorViewState = { focused: 'input' }; constructor( public readonly resource: URI, @@ -54,7 +59,6 @@ export class SearchEditorInput extends EditorInput { @IEditorService protected readonly editorService: IEditorService, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, - @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -72,43 +76,37 @@ export class SearchEditorInput extends EditorInput { onDidChangeContent: this.onDidChangeDirty, isDirty: () => this.isDirty(), backup: () => this.backup(), - save: (options) => this.save(0, options), - revert: () => this.revert(), + save: (options) => this.save(0, options).then(editor => !!editor), + revert: () => this.revert(0), }; this.workingCopyService.registerWorkingCopy(workingCopyAdapter); } - async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + getResource() { + return this.resource; + } + + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { - const path = await this.promptForPath(this.resource, await this.suggestFileName(), options?.availableFileSystems); + const path = await this.fileDialogService.pickFileToSave(await this.suggestFileName(), options?.availableFileSystems); if (path) { if (await this.textFileService.saveAs(this.resource, path, options)) { this.setDirty(false); - if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(path, this.resource)) { - const replacement = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: path }); - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], group); - return true; - } else if (options?.context === SaveContext.EDITOR_CLOSE) { - return true; + if (!isEqual(path, this.resource)) { + return this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: path }); } + return this; } } - return false; + return undefined; } else { this.setDirty(false); - return !!this.textFileService.write(this.resource, (await this.model).getValue(), options); + const res = await !!this.textFileService.write(this.resource, (await this.model).getValue(), options); + return res ? this : undefined; } } - // Brining this over from textFileService because it only suggests for untitled scheme. - // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... - private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); - } - getTypeId(): string { return SearchEditorInput.ID; } @@ -171,9 +169,9 @@ export class SearchEditorInput extends EditorInput { return false; } - async revert(options?: IRevertOptions) { + async revert(group: GroupIdentifier, options?: IRevertOptions) { // TODO: this should actually revert the contents. But it needs to set dirty false. - super.revert(options); + super.revert(group, options); this.setDirty(false); return true; } @@ -193,15 +191,9 @@ export class SearchEditorInput extends EditorInput { const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? network.Schemas.vscodeRemote : network.Schemas.file; - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, searchFileName); - } - - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, searchFileName); + const defaultFilePath = this.fileDialogService.defaultFilePath(schemeFilter); + if (defaultFilePath) { + return joinPath(defaultFilePath, searchFileName); } return URI.from({ scheme: schemeFilter, path: searchFileName }); @@ -249,13 +241,14 @@ export class SearchEditorInputFactory implements IEditorInputFactory { const config = input.getConfigSync(); - return JSON.stringify({ resource, dirty: input.isDirty(), config }); + return JSON.stringify({ resource, dirty: input.isDirty(), config, viewState: input.viewState }); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - const { resource, dirty, config } = JSON.parse(serializedEditorInput); + const { resource, dirty, config, viewState } = JSON.parse(serializedEditorInput); if (config && (config.query !== undefined)) { const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text: serializeSearchConfiguration(config), uri: URI.parse(resource) }); + input.viewState = viewState; input.setDirty(dirty); return input; } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index a0ba9324449..baa12121c5d 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -802,7 +802,7 @@ export class SearchView extends ViewPane { } // Expand until first child is a Match - while (!(next instanceof Match)) { + while (next && !(next instanceof Match)) { if (this.tree.isCollapsed(next)) { this.tree.expand(next); } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 64ac782a467..0e1cceb6b97 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -30,6 +30,7 @@ export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseS export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; +export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index aea2f505d7c..4f7afb964a4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -147,9 +147,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.openPreviousRecentlyUsedEditor', 'workbench.action.openNextRecentlyUsedEditorInGroup', 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - 'workbench.action.quickOpenNextRecentlyUsedEditor', 'workbench.action.quickOpenPreviousRecentlyUsedEditor', - 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup', 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', 'workbench.action.focusActiveEditorGroup', 'workbench.action.focusFirstEditorGroup', diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index cbadd30426a..91dc0c6fcaf 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -243,7 +243,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort)); } else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) { - badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort)); + badge = new ProgressBadge(() => nls.localize('checkingForUpdates', "Checking for Updates...")); clazz = 'progress-badge'; priority = 1; } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5446d32464c..2901974d03c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -41,9 +41,10 @@ import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { areSame } from 'vs/platform/userDataSync/common/settingsMerge'; -import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; import type { IEditorInput } from 'vs/workbench/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const enum AuthStatus { Initializing = 'Initializing', @@ -59,10 +60,18 @@ function getSyncAreaLabel(source: SyncSource): string { case SyncSource.Settings: return localize('settings', "Settings"); case SyncSource.Keybindings: return localize('keybindings', "Keybindings"); case SyncSource.Extensions: return localize('extensions', "Extensions"); - case SyncSource.UIState: return localize('ui state label', "UI State"); + case SyncSource.GlobalState: return localize('ui state label', "UI State"); } } +type FirstTimeSyncClassification = { + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + +type SyncErrorClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { private static readonly ENABLEMENT_SETTING = 'sync.enable'; @@ -90,7 +99,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOutputService private readonly outputService: IOutputService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextModelService textModelResolverService: ITextModelService, + @IPreferencesService private readonly preferencesService: IPreferencesService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -104,6 +115,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.registerActions(); this.initializeActiveAccount().then(_ => { if (isWeb) { @@ -209,7 +221,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo [ { label: localize('show conflicts', "Show Conflicts"), - run: () => this.handleConflicts() + run: () => { + this.telemetryService.publicLog2('sync/showConflicts'); + this.handleConflicts(); + } } ], { @@ -246,6 +261,37 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { + switch (code) { + case UserDataSyncErrorCode.TooManyFailures: + this.telemetryService.publicLog2('sync/errorTooMany'); + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."), + actions: { + primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())] + } + }); + return; + case UserDataSyncErrorCode.TooLarge: + this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source: source! }); + if (source === SyncSource.Keybindings || source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(source); + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'), + actions: { + primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + } + return; + } + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); @@ -321,7 +367,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label: getSyncAreaLabel(SyncSource.Extensions) }, { id: 'sync.enableUIState', - label: getSyncAreaLabel(SyncSource.UIState), + label: getSyncAreaLabel(SyncSource.GlobalState), description: localize('ui state description', "Display Language (Only)") }]; } @@ -385,9 +431,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } ); switch (result.choice) { - case 0: await this.userDataSyncService.sync(); break; - case 1: throw canceled(); - case 2: await this.userDataSyncService.pull(); break; + case 0: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); + await this.userDataSyncService.sync(); + break; + case 1: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' }); + throw canceled(); + case 2: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' }); + await this.userDataSyncService.pull(); + break; } } @@ -406,16 +460,21 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }); if (result.confirmed) { - await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + await this.disableSync(); if (result.checkboxChecked) { + this.telemetryService.publicLog2('sync/turnOffEveryWhere'); await this.userDataSyncService.reset(); } } } + private disableSync(): Promise { + return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + } + private async signIn(): Promise { try { - this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId); + this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']); } catch (e) { this.notificationService.error(e); throw e; @@ -455,7 +514,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (previewResource) { const remoteContentResource = toRemoteContentResource(this.userDataSyncService.conflictsSource!); - const editor = await this.editorService.openEditor({ + await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, label, @@ -465,27 +524,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo revealIfVisible: true, }, }); - if (editor?.input) { - const disposable = editor.input.onDispose(async () => { - disposable.dispose(); - const source = getSyncSourceFromRemoteContentResource(remoteContentResource); - if (source === undefined || this.userDataSyncService.conflictsSource !== source) { - return; - } - - const remoteModelRef = await this.textModelResolverService.createModelReference(remoteContentResource); - const previewModelRef = await this.textModelResolverService.createModelReference(previewResource!); - const remoteModelContent = remoteModelRef.object.textEditorModel.getValue(); - const preivewContent = previewModelRef.object.textEditorModel.getValue(); - remoteModelRef.dispose(); - previewModelRef.dispose(); - if (remoteModelContent !== preivewContent - || (source === SyncSource.Settings && !areSame(remoteModelContent, preivewContent, getIgnoredSettings(this.configurationService)))) { - return; - } - await this.userDataSyncService.resolveConflictsAndContinueSync(preivewContent); - }); - } } } @@ -645,6 +683,11 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { } } +type SyncConflictsClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + class AcceptChangesContribution extends Disposable implements IEditorContribution { static get(editor: ICodeEditor): AcceptChangesContribution { @@ -663,6 +706,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); @@ -723,6 +767,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio if (model) { const conflictsSource = this.userDataSyncService.conflictsSource; const syncSource = getSyncSourceFromRemoteContentResource(model.uri); + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'replaceLocal' : 'apply' }); if (syncSource !== undefined) { const syncAreaLabel = getSyncAreaLabel(syncSource); const result = await this.dialogService.confirm({ diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 138707c9a9b..63c9af47e20 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -452,7 +452,7 @@ const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 - // check new scrollY and reset if neccessary + // check new scrollY and reset if necessary setInitialScrollPosition(contentDocument.body, contentWindow); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index ec0ee6fa63b..770b2038393 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -39,9 +39,11 @@ export class WebviewInput extends EditorInput { } dispose() { - if (!this._didSomeoneTakeMyWebview) { - this._webview?.rawValue?.dispose(); - this._onDisposeWebview.fire(); + if (!this.isDisposed()) { + if (!this._didSomeoneTakeMyWebview) { + this._webview?.rawValue?.dispose(); + this._onDisposeWebview.fire(); + } } super.dispose(); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 00aa8576189..929b367097b 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron'; +import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; import { addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -65,8 +65,8 @@ class WebviewTagHandle extends Disposable { } } -type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise; -type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined; +type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise; +type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined; class WebviewSession extends Disposable { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 65d95f95ecd..07f56351ccc 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -537,13 +537,13 @@ export class ElectronWindow extends Disposable { } // base options with product info - const options: CrashReporterStartOptions = { + const options = { companyName, productName, submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, extra: { vscode_version: product.version, - vscode_commit: product.commit || '' + vscode_commit: product.commit } }; diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index e384482de42..57b75baa80b 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -25,7 +25,7 @@ export interface IAuthenticationService { readonly onDidChangeSessions: Event; getSessions(providerId: string): Promise | undefined>; getDisplayName(providerId: string): string; - login(providerId: string): Promise; + login(providerId: string, scopes: string[]): Promise; logout(providerId: string, accountId: string): Promise; } @@ -79,10 +79,10 @@ export class AuthenticationService extends Disposable implements IAuthentication return undefined; } - async login(id: string): Promise { + async login(id: string, scopes: string[]): Promise { const authProvider = this._authenticationProviders.get(id); if (authProvider) { - return authProvider.login(); + return authProvider.login(scopes); } else { throw new Error(`No authentication provider '${id}' is currently registered.`); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index e946a5dfc90..c3475bd870a 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -27,6 +27,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -681,13 +682,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { // editors are potentially bringing up some UI and thus run // sequentially. const editorsToSaveParallel: IEditorIdentifier[] = []; - const editorsToSaveAsSequentially: IEditorIdentifier[] = []; + const editorsToSaveSequentially: IEditorIdentifier[] = []; if (options?.saveAs) { - editorsToSaveAsSequentially.push(...editors); + editorsToSaveSequentially.push(...editors); } else { for (const { groupId, editor } of editors) { if (editor.isUntitled()) { - editorsToSaveAsSequentially.push({ groupId, editor }); + editorsToSaveSequentially.push({ groupId, editor }); } else { editorsToSaveParallel.push({ groupId, editor }); } @@ -707,15 +708,34 @@ export class EditorService extends Disposable implements EditorServiceImpl { })); // Editors to save sequentially - for (const { groupId, editor } of editorsToSaveAsSequentially) { + for (const { groupId, editor } of editorsToSaveSequentially) { if (editor.isDisposed()) { continue; // might have been disposed from from the save already } + // Preserve view state by opening the editor first if the editor + // is untitled or we "Save As". This also allows the user to review + // the contents of the editor before making a decision. + let viewState: IEditorViewState | undefined = undefined; + const control = await this.openEditor(editor, undefined, groupId); + if (isTextEditor(control)) { + viewState = control.getViewState(); + } + const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); if (!result) { return false; // failed or cancelled, abort } + + // Replace editor preserving viewstate (either across all groups or + // only selected group) if the resulting editor is different from the + // current one. + if (!result.matches(editor)) { + const targetGroups = editor.isUntitled() ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [groupId]; + for (const group of targetGroups) { + await this.replaceEditors([{ editor, replacement: result, options: { pinned: true, viewState } }], group); + } + } } return true; @@ -737,7 +757,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Use revert as a hint to pin the editor this.editorGroupService.getGroup(groupId)?.pinEditor(editor); - return editor.revert(options); + return editor.revert(groupId, options); })); return result.every(success => !!success); diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 8c507a32df9..5f6bb85eff3 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -55,7 +55,7 @@ export interface ISaveEditorsOptions extends ISaveOptions { export interface IBaseSaveRevertAllEditorOptions { /** - * Wether to include untitled editors as well. + * Whether to include untitled editors as well. */ includeUntitled?: boolean; } diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 041ae8729b8..ddec69f8f72 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IEditorInput } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -72,15 +72,15 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { setFailToOpen(): void { this.fails = true; } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSaved = true; - return true; + return this; } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSavedAs = true; - return true; + return this; } - async revert(options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; this.gotSaved = false; this.gotSavedAs = false; diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index dfb857d8350..2d5d095b490 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -43,7 +43,7 @@ export interface IWorkspaceProvider { * * @param workspace the workspace to open. * @param options optional options for the workspace to open. - * - `reuse`: wether to open inside the current window or a new window + * - `reuse`: whether to open inside the current window or a new window * - `payload`: arbitrary payload that should be made available * to the opening window via the `IWorkspaceProvider.payload` property. */ diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 501a673894f..4201d713077 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -15,6 +15,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export class PreferencesEditorInput extends SideBySideEditorInput { static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; @@ -34,9 +35,10 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { defaultSettingsResource: URI, @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService, editorGroupService); } getTypeId(): string { diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index cd91a975bda..91519f20cbd 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -53,7 +53,7 @@ export interface IStatusbarEntry { readonly arguments?: any[]; /** - * Wether to show a beak above the status bar entry. + * Whether to show a beak above the status bar entry. */ readonly showBeak?: boolean; } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 3090dcfe774..57c9f401b6e 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -18,12 +18,10 @@ import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/commo import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { isEqualOrParent, isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -34,6 +32,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { coalesce } from 'vs/base/common/arrays'; +import { suggestFilename } from 'vs/base/common/mime'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -65,10 +64,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IInstantiationService protected readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @IHistoryService private readonly historyService: IHistoryService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, @ITextModelService private readonly textModelService: ITextModelService, @@ -298,7 +295,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region save - async save(resource: URI, options?: ITextFileSaveOptions): Promise { + async save(resource: URI, options?: ITextFileSaveOptions): Promise { // Untitled if (resource.scheme === Schemas.untitled) { @@ -308,19 +305,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled with associated file path don't need to prompt if (model.hasAssociatedFilePath) { - targetUri = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); + targetUri = this.suggestSavePath(resource); } // Otherwise ask user else { - targetUri = await this.promptForPath(resource, this.suggestFilePath(resource)); + targetUri = await this.fileDialogService.pickFileToSave(this.suggestSavePath(resource), options?.availableFileSystems); } // Save as if target provided if (targetUri) { - await this.saveAs(resource, targetUri, options); - - return true; + return this.saveAs(resource, targetUri, options); } } } @@ -333,19 +328,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Save with options await model.save(options); - return !model.isDirty(); + return !model.isDirty() ? resource : undefined; } } - return false; - } - - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); + return undefined; } private getFileModels(resources?: URI[]): ITextFileEditorModel[] { @@ -364,12 +351,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Get to target resource if (!target) { - let dialogPath = source; - if (source.scheme === Schemas.untitled) { - dialogPath = this.suggestFilePath(source); - } - - target = await this.promptForPath(source, dialogPath, options?.availableFileSystems); + target = await this.fileDialogService.pickFileToSave(this.suggestSavePath(source), options?.availableFileSystems); } if (!target) { @@ -378,9 +360,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Just save if target is same as models own resource if (source.toString() === target.toString()) { - await this.save(source, options); - - return source; + return this.save(source, options); } // Do it @@ -550,23 +530,49 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return (await this.dialogService.confirm(confirm)).confirmed; } - private suggestFilePath(untitledResource: URI): URI { - const untitledFileName = this.untitled.get(untitledResource)?.suggestFileName() ?? basename(untitledResource); + private suggestSavePath(resource: URI): URI { + + // Just take the resource as is if the file service can handle it + if (this.fileService.canHandleResource(resource)) { + return resource; + } + const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, untitledFileName); + // Otherwise try to suggest a path that can be saved + let suggestedFilename: string | undefined = undefined; + if (resource.scheme === Schemas.untitled) { + const model = this.untitledTextEditorService.get(resource); + if (model) { + + // Untitled with associated file path + if (model.hasAssociatedFilePath) { + return toLocalResource(resource, remoteAuthority); + } + + // Untitled without associated file path + const mode = model.getMode(); + if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text + suggestedFilename = suggestFilename(mode, model.getName()); + } else { + suggestedFilename = model.getName(); + } + } } - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, untitledFileName); + // Fallback to basename of resource + if (!suggestedFilename) { + suggestedFilename = basename(resource); } - return untitledResource.with({ path: untitledFileName }); + // Try to place where last active file was if any + const defaultFilePath = this.fileDialogService.defaultFilePath(); + if (defaultFilePath) { + return joinPath(defaultFilePath, suggestedFilename); + } + + // Finally fallback to suggest just the file name + return toLocalResource(resource.with({ path: suggestedFilename }), remoteAuthority); } //#endregion @@ -577,7 +583,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled if (resource.scheme === Schemas.untitled) { - const model = this.untitled.get(resource); + const model = this.untitled.exists(resource) ? await this.untitled.resolve({ untitledResource: resource }) : undefined; if (model) { return model.revert(options); } @@ -600,26 +606,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }); await Promise.all(fileModels.map(async model => { - try { - await model.revert(options); - // If model is still dirty, mark the resulting operation as error - if (model.isDirty()) { - const result = mapResourceToResult.get(model.resource); - if (result) { - result.error = true; - } - } - } catch (error) { + // Revert through model + await model.revert(options); - // FileNotFound means the file got deleted meanwhile, so ignore it - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - return; - } - - // Otherwise bubble up the error - else { - throw error; + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); + if (result) { + result.error = true; } } })); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index d0f22c9fb60..fb23bde3b4f 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -230,10 +230,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil await this.load({ forceReadFromDisk: true }); } catch (error) { - // Set flags back to previous values, we are still dirty if revert failed - undo(); + // FileNotFound means the file got deleted meanwhile, so ignore it + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - throw error; + // Set flags back to previous values, we are still dirty if revert failed + undo(); + + throw error; + } } } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 95394f9471c..2b812b8476e 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -62,9 +62,9 @@ export interface ITextFileService extends IDisposable { * * @param resource the resource to save * @param options optional save options - * @return true if the resource was saved. + * @return Path of the saved resource or undefined if canceled. */ - save(resource: URI, options?: ISaveOptions): Promise; + save(resource: URI, options?: ISaveOptions): Promise; /** * Saves the provided resource asking the user for a file name or using the provided one. @@ -72,7 +72,7 @@ export interface ITextFileService extends IDisposable { * @param resource the resource to save as. * @param targetResource the optional target to save to. * @param options optional save options - * @return Path of the saved resource. + * @return Path of the saved resource or undefined if canceled. */ saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise; @@ -174,7 +174,7 @@ export interface IWriteTextFileOptions extends IWriteFileOptions { overwriteReadonly?: boolean; /** - * Wether to write to the file as elevated (admin) user. When setting this option a prompt will + * Whether to write to the file as elevated (admin) user. When setting this option a prompt will * ask the user to authenticate as super user. */ writeElevated?: boolean; @@ -255,7 +255,7 @@ export const enum ModelState { /** * Any error that happens during a save that is not causing the CONFLICT state. - * Models in error mode are always diry. + * Models in error mode are always dirty. */ ERROR } diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index aa380e043fe..ec01c5e5fe2 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -32,9 +32,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -49,17 +47,15 @@ export class NativeTextFileService extends AbstractTextFileService { @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, - @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IProductService private readonly productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 16f4b6e7f4f..ff656aaf791 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -81,7 +81,7 @@ suite('Files - TextFileService', () => { assert.ok(accessor.textFileService.isDirty(model.resource)); const res = await accessor.textFileService.save(model.resource); - assert.ok(res); + assert.equal(res?.toString(), model.resource.toString()); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); @@ -94,14 +94,14 @@ suite('Files - TextFileService', () => { assert.ok(accessor.textFileService.isDirty(model.resource)); const res = await accessor.textFileService.save(model.resource); - assert.ok(res); + assert.equal(res?.toString(), model.resource.toString()); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('saveAs - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - accessor.textFileService.setPromptPath(model.resource); + accessor.fileDialogService.setPickFileToSave(model.resource); await model.load(); model.textEditorModel!.setValue('foo'); @@ -115,7 +115,7 @@ suite('Files - TextFileService', () => { test('revert - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - accessor.textFileService.setPromptPath(model.resource); + accessor.fileDialogService.setPickFileToSave(model.resource); await model.load(); model!.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 57b6da6ea10..cec284d34d7 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -139,7 +139,7 @@ export class TextModelResolverService implements ITextModelService { private async doCreateModelReference(resource: URI): Promise> { - // Untitled Schema: go through cached input + // Untitled Schema: go through untitled text service if (resource.scheme === network.Schemas.untitled) { const model = await this.untitledTextEditorService.resolve({ untitledResource: resource }); @@ -149,7 +149,6 @@ export class TextModelResolverService implements ITextModelService { // InMemory Schema: go through model service cache if (resource.scheme === network.Schemas.inMemory) { const cachedModel = this.modelService.getModel(resource); - if (!cachedModel) { throw new Error('Cant resolve inmemory resource'); } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 8dfd05c4a86..8b27b6fc35e 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -19,7 +19,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/modes'; @@ -40,6 +40,9 @@ const tokenGroupToScopesMap = { }; +export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; +export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; + export class ColorThemeData implements IColorTheme { id: string; @@ -122,7 +125,7 @@ export class ColorThemeData implements IColorTheme { return color; } - public getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { + public getTokenStyle(classification: TokenClassification, useDefault = true, definitions: TokenStyleDefinitions = {}): TokenStyle | undefined { let result: any = { foreground: undefined, bold: undefined, @@ -136,10 +139,11 @@ export class ColorThemeData implements IColorTheme { italic: -1 }; - function _processStyle(matchScore: number, style: TokenStyle) { + function _processStyle(matchScore: number, style: TokenStyle, definition: TokenStyleDefinition) { if (style.foreground && score.foreground <= matchScore) { score.foreground = matchScore; result.foreground = style.foreground; + definitions.foreground = definition; } for (let p of ['bold', 'underline', 'italic']) { const property = p as keyof TokenStyle; @@ -148,6 +152,7 @@ export class ColorThemeData implements IColorTheme { if (score[property] <= matchScore) { score[property] = matchScore; result[property] = info; + definitions[property] = definition; } } } @@ -159,12 +164,16 @@ export class ColorThemeData implements IColorTheme { let style: TokenStyle | undefined; if (rule.defaults.scopesToProbe) { style = this.resolveScopes(rule.defaults.scopesToProbe); + if (style) { + _processStyle(matchScore, style, rule.defaults.scopesToProbe); + } } if (!style && useDefault !== false) { - style = this.resolveTokenStyleValue(rule.defaults[this.type]); - } - if (style) { - _processStyle(matchScore, style); + const tokenStyleValue = rule.defaults[this.type]; + style = this.resolveTokenStyleValue(tokenStyleValue); + if (style) { + _processStyle(matchScore, style, tokenStyleValue!); + } } } } @@ -172,14 +181,14 @@ export class ColorThemeData implements IColorTheme { for (const rule of this.tokenStylingRules) { const matchScore = rule.match(classification); if (matchScore >= 0) { - _processStyle(matchScore, rule.value); + _processStyle(matchScore, rule.value, rule); } } } for (const rule of this.customTokenStylingRules) { const matchScore = rule.match(classification); if (matchScore >= 0) { - _processStyle(matchScore, rule.value); + _processStyle(matchScore, rule.value, rule); } } return TokenStyle.fromData(result); @@ -234,12 +243,12 @@ export class ColorThemeData implements IColorTheme { return this.getTokenColorIndex().asArray(); } - public getTokenStyleMetadata(type: string, modifiers: string[], useDefault?: boolean): number | undefined { + public getTokenStyleMetadata(type: string, modifiers: string[], useDefault = true, definitions: TokenStyleDefinitions = {}): number | undefined { const classification = tokenClassificationRegistry.getTokenClassification(type, modifiers); if (!classification) { return undefined; } - const style = this.getTokenStyle(classification, useDefault); + const style = this.getTokenStyle(classification, useDefault, definitions); let fontStyle = FontStyle.None; let foreground = 0; if (style) { @@ -257,6 +266,16 @@ export class ColorThemeData implements IColorTheme { return toMetadata(fontStyle, foreground, 0); } + public getTokenStylingRuleScope(rule: TokenStylingRule): 'setting' | 'theme' | undefined { + if (this.customTokenStylingRules.indexOf(rule) !== -1) { + return 'setting'; + } + if (this.tokenStylingRules && this.tokenStylingRules.indexOf(rule) !== -1) { + return 'theme'; + } + return undefined; + } + public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } @@ -769,18 +788,19 @@ function normalizeColor(color: string | Color | undefined | null): string | unde if (typeof color !== 'string') { color = Color.Format.CSS.formatHexA(color, true); } - if (color.charCodeAt(0) !== CharCode.Hash) { + const len = color.length; + if (color.charCodeAt(0) !== CharCode.Hash || (len !== 4 && len !== 5 && len !== 7 && len !== 9)) { return undefined; } let result = [CharCode.Hash]; - const len = color.length; + for (let i = 1; i < len; i++) { const upper = hexUpper(color.charCodeAt(i)); if (!upper) { return undefined; } result.push(upper); - if (len === 3 || len === 4) { + if (len === 4 || len === 5) { result.push(upper); } } @@ -788,10 +808,7 @@ function normalizeColor(color: string | Color | undefined | null): string | unde if (result.length === 9 && result[7] === CharCode.F && result[8] === CharCode.F) { result.length = 7; } - if (result.length === 7) { - return String.fromCharCode(...result); - } - return undefined; + return String.fromCharCode(...result); } function hexUpper(charCode: CharCode): number { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index d82687618ff..7609e32cfe7 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -140,7 +140,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; - protected readonly mapResourceToInput = new ResourceMap(); + private readonly mapResourceToInput = new ResourceMap(); private readonly mapResourceToAssociatedFilePath = new ResourceMap(); constructor( diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index f773ebf8be1..7976bb0fbdb 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } 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'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event } from 'vs/base/common/event'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { _serviceBrand: undefined; private readonly channel: IChannel; + get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index c0fda78d2f9..43a904996c6 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -9,7 +9,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -91,10 +90,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('isFirstTimeSyncAndHasUserData'); } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.channel.call('removeExtension', [identifier]); - } - private async updateStatus(status: SyncStatus): Promise { this._conflictsSource = await this.channel.call('getConflictsSource'); this._status = status; diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index c9f7a35bf79..dea9b896e53 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -61,13 +61,12 @@ function groupListener(group: EditorGroup): GroupEvents { disposed: [] }; - group.onDidEditorOpen(e => groupEvents.opened.push(e)); - group.onDidEditorClose(e => groupEvents.closed.push(e)); - group.onDidEditorActivate(e => groupEvents.activated.push(e)); - group.onDidEditorPin(e => groupEvents.pinned.push(e)); - group.onDidEditorUnpin(e => groupEvents.unpinned.push(e)); - group.onDidEditorMove(e => groupEvents.moved.push(e)); - group.onDidEditorDispose(e => groupEvents.disposed.push(e)); + group.onDidOpenEditor(e => groupEvents.opened.push(e)); + group.onDidCloseEditor(e => groupEvents.closed.push(e)); + group.onDidActivateEditor(e => groupEvents.activated.push(e)); + group.onDidChangeEditorPinned(e => group.isPinned(e) ? groupEvents.pinned.push(e) : groupEvents.unpinned.push(e)); + group.onDidMoveEditor(e => groupEvents.moved.push(e)); + group.onDidDisposeEditor(e => groupEvents.disposed.push(e)); return groupEvents; } @@ -1340,12 +1339,12 @@ suite('Workbench editor groups', () => { group2.openEditor(input2, { pinned: true, active: true }); let dirty1Counter = 0; - group1.onDidEditorBecomeDirty(() => { + group1.onDidChangeEditorDirty(() => { dirty1Counter++; }); let dirty2Counter = 0; - group2.onDidEditorBecomeDirty(() => { + group2.onDidChangeEditorDirty(() => { dirty2Counter++; }); diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index c5b76cba3b2..d452310ba74 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -43,11 +43,12 @@ suite('Workbench untitled text editors', () => { (accessor.untitledTextEditorService as UntitledTextEditorService).dispose(); }); - test('basics', async (done) => { + test('basics', async () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; const input1 = service.create(); + await input1.resolve(); assert.equal(input1, service.create({ untitledResource: input1.getResource() })); assert.equal(service.get(input1.getResource()), input1); @@ -62,52 +63,58 @@ suite('Workbench untitled text editors', () => { assert.equal(service.get(input2.getResource()), input2); // revert() - input1.revert(); + await input1.revert(0); assert.ok(input1.isDisposed()); assert.ok(!service.get(input1.getResource())); // dirty const model = await input2.resolve(); assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model); + assert.ok(service.exists(model.resource)); assert.ok(!input2.isDirty()); - const listener = service.onDidChangeDirty(resource => { - listener.dispose(); - - assert.equal(resource.toString(), input2.getResource().toString()); - - assert.ok(input2.isDirty()); - - assert.ok(workingCopyService.isDirty(input2.getResource())); - assert.equal(workingCopyService.dirtyCount, 1); - - input1.revert(); - input2.revert(); - assert.ok(!service.get(input1.getResource())); - assert.ok(!service.get(input2.getResource())); - assert.ok(!input2.isDirty()); - assert.ok(!model.isDirty()); - - assert.ok(!workingCopyService.isDirty(input2.getResource())); - assert.equal(workingCopyService.dirtyCount, 0); - - assert.ok(input1.revert()); - assert.ok(input1.isDisposed()); - assert.ok(!service.exists(input1.getResource())); - - input2.dispose(); - assert.ok(!service.exists(input2.getResource())); - - done(); - }); + const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService); model.textEditorModel.setValue('foo bar'); - model.dispose(); - input1.dispose(); + + const resource = await resourcePromise; + + assert.equal(resource.toString(), input2.getResource().toString()); + + assert.ok(input2.isDirty()); + + assert.ok(workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 1); + + await input1.revert(0); + await input2.revert(0); + assert.ok(!service.get(input1.getResource())); + assert.ok(!service.get(input2.getResource())); + assert.ok(!input2.isDirty()); + assert.ok(!model.isDirty()); + + assert.ok(!workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 0); + + assert.equal(await input1.revert(0), false); + assert.ok(input1.isDisposed()); + assert.ok(!service.exists(input1.getResource())); + input2.dispose(); + assert.ok(!service.exists(input2.getResource())); }); + function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise { + return new Promise(c => { + const listener = service.onDidChangeDirty(async resource => { + listener.dispose(); + + c(resource); + }); + }); + } + test('associated resource is dirty', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); @@ -168,14 +175,6 @@ suite('Workbench untitled text editors', () => { input.dispose(); }); - test('suggest name', function () { - const service = accessor.untitledTextEditorService; - const input = service.create(); - - assert.ok(input.suggestFileName().length > 0); - input.dispose(); - }); - test('associated path remains dirty when content gets empty', async () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); @@ -367,6 +366,23 @@ suite('Workbench untitled text editors', () => { model.dispose(); }); + test('model#onDispose when reverted', async function () { + const service = accessor.untitledTextEditorService; + const input = service.create(); + + let counter = 0; + + const model = await input.resolve(); + model.onDispose(() => counter++); + + model.textEditorModel.setValue('foo'); + + await model.revert(); + + assert.ok(input.isDisposed()); + assert.ok(counter > 1); + }); + test('model#onDidChangeName and input name', async function () { const service = accessor.untitledTextEditorService; const input = service.create(); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index bca2b4ab1ca..4075d675593 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -202,7 +202,6 @@ export class TestContextService implements IWorkspaceContextService { } export class TestTextFileService extends NativeTextFileService { - private promptPath!: URI; private resolveTextContentError!: FileOperationError | null; constructor( @@ -212,10 +211,8 @@ export class TestTextFileService extends NativeTextFileService { @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, - @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @@ -229,10 +226,8 @@ export class TestTextFileService extends NativeTextFileService { instantiationService, modelService, environmentService, - historyService, dialogService, fileDialogService, - editorService, textResourceConfigurationService, productService, filesConfigurationService, @@ -241,10 +236,6 @@ export class TestTextFileService extends NativeTextFileService { ); } - setPromptPath(path: URI): void { - this.promptPath = path; - } - setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } @@ -269,10 +260,6 @@ export class TestTextFileService extends NativeTextFileService { size: 10 }; } - - promptForPath(_resource: URI, _defaultPath: URI): Promise { - return Promise.resolve(this.promptPath); - } } export interface ITestInstantiationService extends IInstantiationService { @@ -425,8 +412,12 @@ export class TestFileDialogService implements IFileDialogService { pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } + private fileToSave!: URI; + setPickFileToSave(path: URI): void { + this.fileToSave = path; + } pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { - return Promise.resolve(undefined); + return Promise.resolve(this.fileToSave); } showSaveDialog(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); diff --git a/yarn.lock b/yarn.lock index 334e3f30543..54b64c3195c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -140,38 +140,11 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@electron/get@^1.0.1": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd" - integrity sha512-LSE4LZGMjGS9TloDx0yO44D2UTbaeKRk+QjlhWLiQlikV6J4spgDCjb6z4YIcqmPAwNzlNCnWF4dubytwI+ATA== - dependencies: - debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^9.6.0" - sanitize-filename "^1.6.2" - sumchecker "^3.0.1" - optionalDependencies: - global-agent "^2.0.2" - global-tunnel-ng "^2.7.1" - "@istanbuljs/schema@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@types/applicationinsights@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" @@ -249,10 +222,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^12.0.12": - version "12.12.24" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" - integrity sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug== +"@types/node@^10.12.18": + version "10.17.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" + integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== "@types/node@^12.11.7": version "12.12.14" @@ -902,6 +875,11 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1272,11 +1250,6 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boolean@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.0.tgz#fab78d5907dbae6216ab46d32733bb7b76b99e76" - integrity sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ== - boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1521,24 +1494,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - callsites@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1797,13 +1770,6 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -2026,7 +1992,7 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" -config-chain@^1.1.11, config-chain@^1.1.12: +config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2137,11 +2103,6 @@ copy-webpack-plugin@^4.5.2: p-limit "^1.0.0" serialize-javascript "^1.4.0" -core-js@^3.4.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.1.tgz#39d5e2e346258cc01eb7d44345b1c3c014ca3f05" - integrity sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ== - core-util-is@1.0.2, 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" @@ -2326,6 +2287,13 @@ cuint@^0.2.1: resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -2362,7 +2330,7 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2376,7 +2344,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.0.0, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2447,12 +2415,7 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -defer-to-connect@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" - integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== - -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2552,11 +2515,6 @@ detect-libc@^1.0.2, detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -2656,11 +2614,6 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2730,18 +2683,33 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-download@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" + integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== + dependencies: + debug "^3.0.0" + env-paths "^1.0.0" + fs-extra "^4.0.1" + minimist "^1.2.0" + nugget "^2.0.1" + path-exists "^3.0.0" + rc "^1.2.1" + semver "^5.4.1" + sumchecker "^2.0.2" + electron-to-chromium@^1.2.7: version "1.3.27" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@7.1.7: - version "7.1.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.7.tgz#520e2bc422e3dfd4bae166dd3be62101f2cbdc52" - integrity sha512-aCLJ4BJwnvOckJgovNul22AYlMFDzm4S4KqKCG2iBlFJyMHBxXAKFKMsgYd40LBZWS3hcY6RHpaYjHSAPLS1pw== +electron@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.6.tgz#d63ea9c89b85b981a29eb3088bf391bf52bd8d73" + integrity sha512-4c4GiFTbWY2Mgv20HB4Bfhf1hDKb0MWgC35wkwNepNom1ioWfumocPHZrSs1xNAEe+tOmezY6lq74n3LbwTnVQ== dependencies: - "@electron/get" "^1.0.1" - "@types/node" "^12.0.12" + "@types/node" "^10.12.18" + electron-download "^4.1.0" extract-zip "^1.0.3" elliptic@^6.0.0: @@ -2779,11 +2747,6 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -encodeurl@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -2810,10 +2773,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== +env-paths@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" + integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -2837,11 +2800,6 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-iterator "~2.0.1" es6-symbol "~3.1.1" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - es6-iterator@^2.0.1, es6-iterator@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2896,11 +2854,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" @@ -3661,6 +3614,15 @@ fs-extra@0.26.7: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -3670,15 +3632,6 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -3791,20 +3744,18 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-stream@^4.0.0, get-stream@^4.1.0: +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3970,19 +3921,6 @@ glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-agent@^2.0.2: - version "2.1.7" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.7.tgz#12d7bc2b07cd862d0fa76b0f1b2c48cd5ffcf150" - integrity sha512-ooK7eqGYZku+LgnbfH/Iv0RJ74XfhrBZDlke1QSzcBt0bw1PmJcnRADPAQuFE+R45pKKDTynAr25SBasY2kvow== - dependencies: - boolean "^3.0.0" - core-js "^3.4.1" - es6-error "^4.1.1" - matcher "^2.0.0" - roarr "^2.14.5" - semver "^6.3.0" - serialize-error "^5.0.0" - global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -4019,16 +3957,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global-tunnel-ng@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" - integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== - dependencies: - encodeurl "^1.0.2" - lodash "^4.17.10" - npm-conf "^1.1.3" - tunnel "^0.0.6" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4046,13 +3974,6 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globalthis@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" - integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== - dependencies: - define-properties "^1.1.3" - globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -4084,33 +4005,11 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -4573,11 +4472,6 @@ html-escaper@^2.0.0: inherits "^2.0.1" readable-stream "^2.0.2" -http-cache-semantics@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" - integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== - http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -4711,6 +4605,13 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -4966,6 +4867,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + 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" @@ -5361,11 +5269,6 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-edm-parser@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" @@ -5405,7 +5308,7 @@ json-stable-stringify@^1.0.0: dependencies: jsonify "~0.0.0" -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -5481,13 +5384,6 @@ keytar@^4.11.0: nan "2.14.0" prebuild-install "5.3.0" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -5761,15 +5657,13 @@ long@^3.2.0: resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" lru-cache@2: version "2.7.3" @@ -5835,6 +5729,11 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + map-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -5873,13 +5772,6 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" -matcher@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.1.0.tgz#64e1041c15b993e23b786f93320a7474bf833c28" - integrity sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ== - dependencies: - escape-string-regexp "^2.0.0" - math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" @@ -5929,6 +5821,22 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^3.1.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6048,11 +5956,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4= -mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -6083,7 +5986,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -6422,6 +6325,16 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" @@ -6459,11 +6372,6 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -6476,14 +6384,6 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow== -npm-conf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - npm-packlist@^1.1.6: version "1.1.11" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" @@ -6516,6 +6416,19 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" +nugget@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" + integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= + dependencies: + debug "^2.1.3" + minimist "^1.1.0" + pretty-bytes "^1.0.2" + progress-stream "^1.1.0" + request "^2.45.0" + single-line-log "^1.1.2" + throttleit "0.0.2" + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -6783,11 +6696,6 @@ p-all@^1.0.0: dependencies: p-map "^1.0.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -7406,16 +7314,19 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +pretty-bytes@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" + integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= + dependencies: + get-stdin "^4.0.1" + meow "^3.1.0" + pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -7444,6 +7355,14 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress-stream@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" + integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= + dependencies: + speedometer "~0.1.2" + through2 "~0.2.3" + progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -7651,7 +7570,7 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" -rc@^1.2.7: +rc@^1.2.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== @@ -7727,7 +7646,7 @@ read@^1.0.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^1.1.8: +readable-stream@^1.1.8, readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -7795,6 +7714,14 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -7874,6 +7801,13 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" @@ -7956,7 +7890,7 @@ request@2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.86.0, request@^2.88.0: +request@^2.45.0, request@^2.86.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -8050,6 +7984,13 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" +resolve@^1.10.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" + integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== + dependencies: + path-parse "^1.0.6" + resolve@^1.3.2: version "1.14.2" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" @@ -8064,13 +8005,6 @@ resolve@^1.4.0: dependencies: path-parse "^1.0.6" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8126,18 +8060,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -roarr@^2.14.5: - version "2.14.6" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.14.6.tgz#cebe8ad7ecbfd15bfa37b02dacf00809dd633912" - integrity sha512-qjbw0BEesKA+3XFBPt+KVe1PC/Z6ShfJ4wPlx2XifqH5h2Lj8/KQT5XJTsy3n1Es5kai+BwKALaECW3F70B1cg== - dependencies: - boolean "^3.0.0" - detect-node "^2.0.4" - globalthis "^1.0.0" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -8198,13 +8120,6 @@ samsam@~1.1: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= -sanitize-filename@^1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" - integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== - dependencies: - truncate-utf8-bytes "^1.0.0" - sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" @@ -8223,11 +8138,6 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -8289,13 +8199,6 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" -serialize-error@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" - integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== - dependencies: - type-fest "^0.8.0" - serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -8405,6 +8308,13 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +single-line-log@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" + integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= + dependencies: + string-width "^1.0.1" + sinon@^1.17.2: version "1.17.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" @@ -8588,6 +8498,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== +speedometer@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" + integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8609,11 +8524,6 @@ split@^1.0.1: dependencies: through "2" -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8859,6 +8769,13 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + strip-json-comments@^2.0.1, 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" @@ -8874,12 +8791,12 @@ sudo-prompt@9.1.1: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== -sumchecker@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" - integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== +sumchecker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" + integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= dependencies: - debug "^4.1.0" + debug "^2.2.0" supports-color@1.2.0: version "1.2.0" @@ -9035,6 +8952,11 @@ textextensions@~1.0.0: resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2" integrity sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI= +throttleit@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= + through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -9067,6 +8989,14 @@ through2@^3.0.0: readable-stream "2 || 3" xtend "~4.0.1" +through2@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" + integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= + dependencies: + readable-stream "~1.1.9" + xtend "~2.1.1" + through2@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" @@ -9155,11 +9085,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -9219,12 +9144,10 @@ tough-cookie@~2.4.3: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -truncate-utf8-bytes@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" - integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= - dependencies: - utf8-byte-length "^1.0.1" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= ts-loader@^4.4.2: version "4.4.2" @@ -9271,11 +9194,6 @@ tunnel@0.0.4: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9288,7 +9206,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.8.0, type-fest@^0.8.1: +type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -9483,13 +9401,6 @@ url-join@^1.1.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9503,11 +9414,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -utf8-byte-length@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" - integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= - 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"