From 4a9c47d3d67b0bce23fa378dcf7205949ec19d19 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Mon, 24 Feb 2020 21:15:52 -0800 Subject: [PATCH] Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317) --- .github/workflows/ci.yml | 34 +- .gitignore | 1 + build/azure-pipelines/product-compile.yml | 20 +- build/gulpfile.editor.js | 45 ++ build/lib/compilation.js | 13 - build/lib/compilation.ts | 15 - build/monaco/api.js | 5 +- build/monaco/api.ts | 5 +- build/monaco/esm.core.js | 21 + build/monaco/monaco.webpack.config.js | 44 ++ build/npm/postinstall.js | 6 +- cglicenses.json | 8 + .../schemas/devContainer.schema.json | 70 ++- extensions/git/src/git.ts | 31 +- extensions/git/src/timelineProvider.ts | 207 ++++---- extensions/github-authentication/.gitignore | 1 + extensions/github-authentication/README.md | 7 + .../build/postinstall.js | 26 + .../extension.webpack.config.js | 20 + extensions/github-authentication/package.json | 33 ++ .../github-authentication/package.nls.json | 4 + .../src/common/clientRegistrar.ts | 87 ++++ .../src/common/keychain.ts | 73 +++ .../src/common/logger.ts | 55 +++ .../github-authentication/src/common/utils.ts | 73 +++ .../github-authentication/src/extension.ts | 43 ++ .../github-authentication/src/github.ts | 113 +++++ .../github-authentication/src/githubServer.ts | 114 +++++ .../src/typings/ref.d.ts | 7 + .../github-authentication/tsconfig.json | 13 + extensions/github-authentication/yarn.lock | 454 ++++++++++++++++++ extensions/vscode-account/media/auth.css | 8 +- package.json | 5 +- src/main.js | 6 +- src/vs/base/browser/touch.ts | 4 +- src/vs/base/browser/ui/dialog/dialog.css | 1 - src/vs/base/browser/ui/dialog/dialog.ts | 6 + src/vs/base/common/iterator.ts | 13 + src/vs/base/node/encoding.ts | 4 - src/vs/base/node/pfs.ts | 16 +- src/vs/base/node/stream.ts | 64 --- src/vs/code/electron-main/app.ts | 179 ++++--- src/vs/code/node/paths.ts | 2 +- .../contentWidgets/contentWidgets.ts | 2 +- .../browser/viewParts/lines/rangeUtil.ts | 7 + .../browser/viewParts/lines/viewLine.ts | 2 +- .../browser/viewParts/minimap/minimap.ts | 6 +- .../common/config/commonEditorConfig.ts | 2 +- src/vs/editor/common/config/editorOptions.ts | 10 + src/vs/editor/common/controller/cursor.ts | 8 +- .../common/standalone/standaloneEnums.ts | 159 +++--- .../common/viewLayout/lineDecorations.ts | 76 ++- .../common/viewLayout/viewLineRenderer.ts | 75 ++- src/vs/editor/contrib/folding/folding.ts | 16 +- src/vs/editor/contrib/gotoError/gotoError.ts | 2 +- .../contrib/gotoError/gotoErrorWidget.ts | 36 +- .../common/viewLayout/lineDecorations.test.ts | 40 +- .../viewLayout/viewLineRenderer.test.ts | 87 ++++ src/vs/monaco.d.ts | 165 ++++--- .../platform/product/common/productService.ts | 2 + .../url/electron-main/electronUrlListener.ts | 33 +- .../userDataSync/common/userDataSync.ts | 12 +- .../common/userDataSyncStoreService.ts | 4 +- .../test/common/userDataSyncClient.ts | 4 + src/vs/vscode.proposed.d.ts | 71 +-- .../api/browser/mainThreadTimeline.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostTimeline.ts | 17 +- src/vs/workbench/api/common/extHostWebview.ts | 2 +- .../browser/actions/layoutActions.ts | 93 +++- src/vs/workbench/browser/labels.ts | 16 +- .../browser/parts/compositeBarActions.ts | 10 +- src/vs/workbench/common/actions.ts | 5 +- .../browser/media/extensionsViewlet.css | 1 + .../preferences/browser/settingsEditor2.ts | 2 +- .../search/browser/search.contribution.ts | 4 +- .../contrib/search/browser/searchActions.ts | 22 +- .../contrib/search/browser/searchView.ts | 31 +- .../contrib/searchEditor/browser/constants.ts | 1 + .../browser/searchEditor.contribution.ts | 10 +- .../searchEditor/browser/searchEditor.ts | 61 ++- .../browser/searchEditorActions.ts | 8 + .../timeline/browser/media/timelinePane.css | 1 + .../timeline/browser/timeline.contribution.ts | 97 +++- .../contrib/timeline/browser/timelinePane.ts | 371 ++++++++++++-- .../contrib/timeline/common/timeline.ts | 28 +- .../timeline/common/timelineService.ts | 18 +- .../browser/userDataSync.contribution.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 61 ++- .../services/history/browser/history.ts | 129 +++-- .../common/untitledTextEditorModel.ts | 2 +- .../test/browser/untitledTextEditor.test.ts | 17 +- yarn.lock | 126 ++++- 93 files changed, 3109 insertions(+), 813 deletions(-) create mode 100644 build/monaco/esm.core.js create mode 100644 build/monaco/monaco.webpack.config.js create mode 100644 extensions/github-authentication/.gitignore create mode 100644 extensions/github-authentication/README.md create mode 100644 extensions/github-authentication/build/postinstall.js create mode 100644 extensions/github-authentication/extension.webpack.config.js create mode 100644 extensions/github-authentication/package.json create mode 100644 extensions/github-authentication/package.nls.json create mode 100644 extensions/github-authentication/src/common/clientRegistrar.ts create mode 100644 extensions/github-authentication/src/common/keychain.ts create mode 100644 extensions/github-authentication/src/common/logger.ts create mode 100644 extensions/github-authentication/src/common/utils.ts create mode 100644 extensions/github-authentication/src/extension.ts create mode 100644 extensions/github-authentication/src/github.ts create mode 100644 extensions/github-authentication/src/githubServer.ts create mode 100644 extensions/github-authentication/src/typings/ref.d.ts create mode 100644 extensions/github-authentication/tsconfig.json create mode 100644 extensions/github-authentication/yarn.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c4c946428..b395bd0a8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,7 +119,33 @@ jobs: # name: Download Built-in Extensions - run: ./scripts/test.sh --tfs "Unit Tests" name: Run Unit Tests (Electron) - # - run: yarn test-browser --browser chromium --browser webkit {{SQL CARBON EDIT}} disable for now @TODO @anthonydresser - # name: Run Unit Tests (Browser) - # - run: ./scripts/test-integration.sh --tfs "Integration Tests" {{SQL CARBON EDIT}} remove step - # name: Run Integration Tests (Electron) + # - run: yarn test-browser --browser chromium --browser webkit + # name: Run Unit Tests (Browser) + # - run: ./scripts/test-integration.sh --tfs "Integration Tests" + # name: Run Integration Tests (Electron) + + # monaco: + # runs-on: ubuntu-latest + # env: + # CHILD_CONCURRENCY: "1" + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # steps: + # - uses: actions/checkout@v1 + # # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + # - run: | + # sudo apt-get update + # sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + # sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + # sudo chmod +x /etc/init.d/xvfb + # sudo update-rc.d xvfb defaults + # sudo service xvfb start + # name: Setup Build Environment + # - uses: actions/setup-node@v1 + # with: + # node-version: 10 + # - run: yarn --frozen-lockfile + # name: Install Dependencies + # - run: yarn monaco-compile-check + # name: Run Monaco Editor Checks + # - run: yarn gulp editor-esm-bundle + # name: Editor Distro & ESM Bundle diff --git a/.gitignore b/.gitignore index 5ade800848..80a99a4952 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ out-editor/ out-editor-src/ out-editor-build/ out-editor-esm/ +out-editor-esm-bundle/ out-editor-min/ out-monaco-editor-core/ out-vscode/ diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index e527b8b134..4891618932 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -77,6 +77,21 @@ steps: yarn postinstall displayName: Run postinstall scripts condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) + env: + OSS_GITHUB_ID: "a5d3c261b032765a78de" + OSS_GITHUB_SECRET: $(oss-github-client-secret) + INSIDERS_GITHUB_ID: "31f02627809389d9f111" + INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) + STABLE_GITHUB_ID: "baa8a44b5e861d918709" + STABLE_GITHUB_SECRET: $(stable-github-client-secret) + EXPLORATION_GITHUB_ID: "94e8376d3a90429aeaea" + EXPLORATION_GITHUB_SECRET: $(exploration-github-client-secret) + VSO_GITHUB_ID: "3d4be8f37a0325b5817d" + VSO_GITHUB_SECRET: $(vso-github-client-secret) + VSO_PPE_GITHUB_ID: "eabf35024dc2e891a492" + VSO_PPE_GITHUB_SECRET: $(vso-ppe-github-client-secret) + VSO_DEV_GITHUB_ID: "84383ebd8a7c5f5efc5c" + VSO_DEV_GITHUB_SECRET: $(vso-dev-github-client-secret) # Mixin must run before optimize, because the CSS loader will # inline small SVGs @@ -116,11 +131,6 @@ steps: yarn gulp minify-vscode-reh-web displayName: Compile condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) - env: - OSS_GITHUB_ID: "a5d3c261b032765a78de" - OSS_GITHUB_SECRET: $(oss-github-client-secret) - INSIDERS_GITHUB_ID: "31f02627809389d9f111" - INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret) - script: | set -e diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 188b5df619..91a067a13d 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -16,6 +16,8 @@ const cp = require('child_process'); const compilation = require('./lib/compilation'); const monacoapi = require('./monaco/api'); const fs = require('fs'); +const webpack = require('webpack'); +const webpackGulp = require('webpack-stream'); let root = path.dirname(__dirname); let sha1 = util.getVersion(root); @@ -358,6 +360,49 @@ gulp.task('editor-distro', ) ); +const bundleEditorESMTask = task.define('editor-esm-bundle-webpack', () => { + const result = es.through(); + + const webpackConfigPath = path.join(root, 'build/monaco/monaco.webpack.config.js'); + + const webpackConfig = { + ...require(webpackConfigPath), + ...{ mode: 'production' } + }; + + const webpackDone = (err, stats) => { + if (err) { + result.emit('error', err); + return; + } + const { compilation } = stats; + if (compilation.errors.length > 0) { + result.emit('error', compilation.errors.join('\n')); + } + if (compilation.warnings.length > 0) { + result.emit('data', compilation.warnings.join('\n')); + } + }; + + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(gulp.dest('out-editor-esm-bundle')); +}); + +gulp.task('editor-esm-bundle', + task.series( + task.parallel( + util.rimraf('out-editor-src'), + util.rimraf('out-editor-esm'), + util.rimraf('out-monaco-editor-core'), + util.rimraf('out-editor-esm-bundle'), + ), + extractEditorSrcTask, + createESMSourcesAndResourcesTask, + compileEditorESMTask, + bundleEditorESMTask, + ) +); + gulp.task('monacodts', task.define('monacodts', () => { const result = monacoapi.execute(); fs.writeFileSync(result.filePath, result.content); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index dccc7132d4..903de37a70 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -74,7 +74,6 @@ function compileTask(src, out, build) { if (src === 'src') { generator.execute(); } - // generateGitHubAuthConfig(); return srcPipe .pipe(generator.stream) .pipe(compile()) @@ -97,18 +96,6 @@ function watchTask(out, build) { } exports.watchTask = watchTask; const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); -/*function generateGitHubAuthConfig() { - const schemes = ['OSS', 'INSIDERS']; - let content: { [key: string]: { id?: string, secret?: string }} = {}; - schemes.forEach(scheme => { - content[scheme] = { - id: process.env[`${scheme}_GITHUB_ID`], - secret: process.env[`${scheme}_GITHUB_SECRET`] - }; - }); - - fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content)); -}*/ class MonacoGenerator { constructor(isWatch) { this._executeSoonTimer = null; diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index fcc574bdeb..6409322743 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -88,8 +88,6 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod generator.execute(); } - // generateGitHubAuthConfig(); - return srcPipe .pipe(generator.stream) .pipe(compile()) @@ -117,19 +115,6 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); -/*function generateGitHubAuthConfig() { - const schemes = ['OSS', 'INSIDERS']; - let content: { [key: string]: { id?: string, secret?: string }} = {}; - schemes.forEach(scheme => { - content[scheme] = { - id: process.env[`${scheme}_GITHUB_ID`], - secret: process.env[`${scheme}_GITHUB_SECRET`] - }; - }); - - fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content)); -}*/ - class MonacoGenerator { private readonly _isWatch: boolean; public readonly stream: NodeJS.ReadWriteStream; diff --git a/build/monaco/api.js b/build/monaco/api.js index 4b56799bfe..8f10ce9e90 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -135,11 +135,12 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, } else { const memberName = member.name.text; + const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); if (isStatic(member)) { - usage.push(`a = ${staticTypeName}.${memberName};`); + usage.push(`a = ${staticTypeName}${memberAccess};`); } else { - usage.push(`a = (<${instanceTypeName}>b).${memberName};`); + usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); } } } diff --git a/build/monaco/api.ts b/build/monaco/api.ts index 607e6ac853..415c15f048 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -167,10 +167,11 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati result = result.replace(memberText, ''); } else { const memberName = (member.name).text; + const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); if (isStatic(member)) { - usage.push(`a = ${staticTypeName}.${memberName};`); + usage.push(`a = ${staticTypeName}${memberAccess};`); } else { - usage.push(`a = (<${instanceTypeName}>b).${memberName};`); + usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); } } } catch (err) { diff --git a/build/monaco/esm.core.js b/build/monaco/esm.core.js new file mode 100644 index 0000000000..553bd76cd7 --- /dev/null +++ b/build/monaco/esm.core.js @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Entry file for webpack bunlding. + +import * as monaco from 'monaco-editor-core'; + +self.MonacoEnvironment = { + getWorkerUrl: function (moduleId, label) { + return './editor.worker.bundle.js'; + } +}; + +monaco.editor.create(document.getElementById('container'), { + value: [ + 'var hello = "hello world";' + ].join('\n'), + language: 'javascript' +}); diff --git a/build/monaco/monaco.webpack.config.js b/build/monaco/monaco.webpack.config.js new file mode 100644 index 0000000000..8230b30b53 --- /dev/null +++ b/build/monaco/monaco.webpack.config.js @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); + +module.exports = { + mode: 'production', + entry: { + 'core': './build/monaco/esm.core.js', + 'editor.worker': './out-monaco-editor-core/esm/vs/editor/editor.worker.js' + }, + output: { + globalObject: 'self', + filename: '[name].bundle.js', + path: path.resolve(__dirname, 'dist') + }, + module: { + rules: [{ + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, { + test: /\.ttf$/, + use: ['file-loader'] + }] + }, + resolve: { + alias: { + 'monaco-editor-core': path.resolve(__dirname, '../../out-monaco-editor-core/esm/vs/editor/editor.main.js'), + } + }, + stats: { + all: false, + modules: true, + maxModules: 0, + errors: true, + warnings: true, + // our additional options + moduleTrace: true, + errorDetails: true, + chunks: true + } +}; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index f202636c87..5df163f163 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -13,7 +13,7 @@ const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'; * @param {*} [opts] */ function yarnInstall(location, opts) { - opts = opts || {}; + opts = opts || { env: process.env }; opts.cwd = location; opts.stdio = 'inherit'; @@ -52,8 +52,6 @@ extensions.forEach(extension => yarnInstall(`extensions/${extension}`)); function yarnInstallBuildDependencies() { // make sure we install the deps of build/lib/watch for the system installed // node, since that is the driver of gulp - //@ts-ignore - const env = Object.assign({}, process.env); const watchPath = path.join(path.dirname(__dirname), 'lib', 'watch'); const yarnrcPath = path.join(watchPath, '.yarnrc'); @@ -66,7 +64,7 @@ target "${target}" runtime "${runtime}"`; fs.writeFileSync(yarnrcPath, yarnrc, 'utf8'); - yarnInstall(watchPath, { env }); + yarnInstall(watchPath); } yarnInstall(`build`); // node modules required for build diff --git a/cglicenses.json b/cglicenses.json index 1e9287cbaf..76e6fe49f0 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -81,5 +81,13 @@ "prependLicenseText": [ "Copyright (c) Microsoft Corporation. All rights reserved." ] + }, + { + // Reason: The license at https://github.com/rbuckton/reflect-metadata/blob/master/LICENSE + // does not include a clear Copyright statement (it's in https://github.com/rbuckton/reflect-metadata/blob/master/CopyrightNotice.txt). + "name": "reflect-metadata", + "prependLicenseText": [ + "Copyright (c) Microsoft Corporation. All rights reserved." + ] } ] diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 0d47b27bec..3cff1bd58f 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -48,6 +48,16 @@ "type": "string", "description": "The user VS Code Server will be started with. The default is the same user as the container." }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, "postCreateCommand": { "type": [ "string", @@ -132,7 +142,7 @@ } } }, - "dockerFileContainer": { + "dockerFileAndContext": { "type": "object", "properties": { "dockerFile": { @@ -148,6 +158,64 @@ "dockerFile" ] }, + "dockerFileContainer": { + "oneOf": [ + { + "type": "object", + "properties": { + "build": { + "type": "object", + "description": "Docker build-related options.", + "allOf": [ + { + "$ref": "#/definitions/dockerFileAndContext" + }, + { + "$ref": "#/definitions/buildOptions" + } + ] + } + }, + "required": [ + "build" + ] + }, + { + "allOf": [ + { + "$ref": "#/definitions/dockerFileAndContext" + }, + { + "type": "object", + "properties": { + "build": { + "description": "Docker build-related options.", + "$ref": "#/definitions/buildOptions" + } + } + } + ] + } + ] + }, + "buildOptions": { + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "Target stage in a multi-stage build." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": [ + "string" + ] + }, + "description": "Build arguments." + } + } + }, "imageContainer": { "type": "object", "properties": { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index e25bd42374..faa524f805 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -50,8 +50,13 @@ interface MutableRemote extends Remote { * Log file options. */ export interface LogFileOptions { - /** Max number of log entries to retrieve. If not specified, the default is 32. */ - readonly maxEntries?: number; + /** Optional. The maximum number of log entries to retrieve. */ + readonly maxEntries?: number | string; + /** Optional. The Git sha (hash) to start retrieving log entries from. */ + readonly hash?: string; + /** Optional. Specifies whether to start retrieving log entries in reverse order. */ + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; } function parseVersion(raw: string): string { @@ -817,8 +822,26 @@ export class Repository { } async logFile(uri: Uri, options?: LogFileOptions): Promise { - const maxEntries = options?.maxEntries ?? 32; - const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--', uri.fsPath]; + const args = ['log', `--format=${COMMIT_FORMAT}`, '-z']; + + if (options?.maxEntries && !options?.reverse) { + args.push(`-n${options.maxEntries}`); + } + + if (options?.hash) { + // If we are reversing, we must add a range (with HEAD) because we are using --ancestry-path for better reverse walking + if (options?.reverse) { + args.push('--reverse', '--ancestry-path', `${options.hash}..HEAD`); + } else { + args.push(options.hash); + } + } + + if (options?.sortByAuthorDate) { + args.push('--author-date-order'); + } + + args.push('--', uri.fsPath); const result = await this.run(args); if (result.exitCode) { diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index aabdbc5215..d240bc8ed5 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -5,7 +5,7 @@ import * as dayjs from 'dayjs'; import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository } from './repository'; import { debounce } from './decorators'; @@ -87,7 +87,7 @@ export class GitTimelineProvider implements TimelineProvider { this._disposable.dispose(); } - async provideTimeline(uri: Uri, _cursor: TimelineCursor, _token: CancellationToken): Promise { + async provideTimeline(uri: Uri, options: TimelineOptions, _token: CancellationToken): Promise { // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); const repo = this._model.getRepository(uri); @@ -112,109 +112,152 @@ export class GitTimelineProvider implements TimelineProvider { // TODO[ECA]: Ensure that the uri is a file -- if not we could get the history of the repo? - const commits = await repo.logFile(uri); + let limit: number | undefined; + if (typeof options.limit === 'string') { + try { + const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit}..`, '--', uri.fsPath]); + if (!result.exitCode) { + // Ask for 1 more than so we can determine if there are more commits + limit = Number(result.stdout) + 1; + } + } + catch { + limit = undefined; + } + } else { + // If we are not getting everything, ask for 1 more than so we can determine if there are more commits + limit = options.limit === undefined ? undefined : options.limit + 1; + } + + + const commits = await repo.logFile(uri, { + maxEntries: limit, + hash: options.cursor, + reverse: options.before, + // sortByAuthorDate: true + }); + + const more = limit === undefined || options.before ? false : commits.length >= limit; + const paging = commits.length ? { + more: more, + cursors: { + before: commits[0]?.hash, + after: commits[commits.length - (more ? 1 : 2)]?.hash + } + } : undefined; + + // If we asked for an extra commit, strip it off + if (limit !== undefined && commits.length >= limit) { + commits.splice(commits.length - 1, 1); + } let dateFormatter: dayjs.Dayjs; const items = commits.map(c => { - dateFormatter = dayjs(c.authorDate); + const date = c.commitDate; // c.authorDate - const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, c.authorDate?.getTime() ?? 0, c.hash, 'git:file:commit'); + dateFormatter = dayjs(date); + + const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, date?.getTime() ?? 0, c.hash, 'git:file:commit'); item.iconPath = new (ThemeIcon as any)('git-commit'); item.description = c.authorName; item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n\n${c.message}`; item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', - arguments: [uri, this.id, item] + arguments: [item, uri, this.id] }; return item; }); - const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); - if (index) { - const date = this._repoStatusDate ?? new Date(); - dateFormatter = dayjs(date); + if (options.cursor === undefined || options.before) { + const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (index) { + const date = this._repoStatusDate ?? new Date(); + dateFormatter = dayjs(date); - let status; - switch (index.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; + let status; + switch (index.type) { + case Status.INDEX_MODIFIED: + status = 'Modified'; + break; + case Status.INDEX_ADDED: + status = 'Added'; + break; + case Status.INDEX_DELETED: + status = 'Deleted'; + break; + case Status.INDEX_RENAMED: + status = 'Renamed'; + break; + case Status.INDEX_COPIED: + status = 'Copied'; + break; + default: + status = ''; + break; + } + + const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index'); + // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + item.description = 'You'; + item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; + item.command = { + title: 'Open Comparison', + command: 'git.timeline.openDiff', + arguments: [item, uri, this.id] + }; + + items.splice(0, 0, item); } - const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index'); - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? - item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = 'You'; - item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; - item.command = { - title: 'Open Comparison', - command: 'git.timeline.openDiff', - arguments: [uri, this.id, item] - }; + const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (working) { + const date = new Date(); + dateFormatter = dayjs(date); - items.push(item); - } + let status; + switch (working.type) { + case Status.INDEX_MODIFIED: + status = 'Modified'; + break; + case Status.INDEX_ADDED: + status = 'Added'; + break; + case Status.INDEX_DELETED: + status = 'Deleted'; + break; + case Status.INDEX_RENAMED: + status = 'Renamed'; + break; + case Status.INDEX_COPIED: + status = 'Copied'; + break; + default: + status = ''; + break; + } + const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working'); + // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + item.description = 'You'; + item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; + item.command = { + title: 'Open Comparison', + command: 'git.timeline.openDiff', + arguments: [item, uri, this.id] + }; - const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); - if (working) { - const date = new Date(); - dateFormatter = dayjs(date); - - let status; - switch (working.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; + items.splice(0, 0, item); } - - const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working'); - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? - item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = 'You'; - item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; - item.command = { - title: 'Open Comparison', - command: 'git.timeline.openDiff', - arguments: [uri, this.id, item] - }; - - items.push(item); } - return { items: items }; + return { + items: items, + paging: paging + }; } private onRepositoriesChanged(_repo: Repository) { @@ -241,6 +284,6 @@ export class GitTimelineProvider implements TimelineProvider { @debounce(500) private fireChanged() { - this._onDidChange.fire({}); + this._onDidChange.fire({ reset: true }); } } diff --git a/extensions/github-authentication/.gitignore b/extensions/github-authentication/.gitignore new file mode 100644 index 0000000000..eab338cd46 --- /dev/null +++ b/extensions/github-authentication/.gitignore @@ -0,0 +1 @@ +src/common/config.json diff --git a/extensions/github-authentication/README.md b/extensions/github-authentication/README.md new file mode 100644 index 0000000000..755e502096 --- /dev/null +++ b/extensions/github-authentication/README.md @@ -0,0 +1,7 @@ +# GitHub Authentication for Visual Studio Code + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +This extension provides support for authenticating to GitHub. diff --git a/extensions/github-authentication/build/postinstall.js b/extensions/github-authentication/build/postinstall.js new file mode 100644 index 0000000000..239783e304 --- /dev/null +++ b/extensions/github-authentication/build/postinstall.js @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const fs = require('fs'); +const path = require('path'); + +const schemes = ['OSS', 'INSIDERS', 'STABLE', 'EXPLORATION', 'VSO', 'VSO_PPE', 'VSO_DEV']; + +function main() { + let content = {}; + + for (const scheme of schemes) { + const id = process.env[`${scheme}_GITHUB_ID`]; + const secret = process.env[`${scheme}_GITHUB_SECRET`]; + + if (id && secret) { + content[scheme] = { id, secret }; + } + } + + fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content)); +} + +main(); diff --git a/extensions/github-authentication/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js new file mode 100644 index 0000000000..aba62f39e2 --- /dev/null +++ b/extensions/github-authentication/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts', + }, + externals: { + 'keytar': 'commonjs keytar' + } +}); diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json new file mode 100644 index 0000000000..254516ee52 --- /dev/null +++ b/extensions/github-authentication/package.json @@ -0,0 +1,33 @@ +{ + "name": "github-authentication", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "version": "0.0.1", + "engines": { + "vscode": "^1.41.0" + }, + "enableProposedApi": true, + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "gulp compile-extension:github-authentication", + "watch": "gulp watch-extension:github-authentication", + "postinstall": "node build/postinstall.js" + }, + "dependencies": { + "uuid": "^3.3.3" + }, + "devDependencies": { + "@types/keytar": "^4.4.2", + "@types/node": "^10.12.21", + "@types/uuid": "^3.4.6", + "typescript": "^3.7.5" + } +} diff --git a/extensions/github-authentication/package.nls.json b/extensions/github-authentication/package.nls.json new file mode 100644 index 0000000000..592a413b9a --- /dev/null +++ b/extensions/github-authentication/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "GitHub Authentication", + "description": "GitHub Authentication Provider" +} diff --git a/extensions/github-authentication/src/common/clientRegistrar.ts b/extensions/github-authentication/src/common/clientRegistrar.ts new file mode 100644 index 0000000000..d5f08c6886 --- /dev/null +++ b/extensions/github-authentication/src/common/clientRegistrar.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Uri } from 'vscode'; + +export interface ClientDetails { + id?: string; + secret?: string; +} + +export interface ClientConfig { + OSS: ClientDetails; + INSIDERS: ClientDetails; + STABLE: ClientDetails; + EXPLORATION: ClientDetails; + + VSO: ClientDetails; + VSO_PPE: ClientDetails; + VSO_DEV: ClientDetails; +} + +export class Registrar { + private _config: ClientConfig; + + constructor() { + try { + this._config = require('./config.json') as ClientConfig; + } catch (e) { + this._config = { + OSS: {}, + INSIDERS: {}, + STABLE: {}, + EXPLORATION: {}, + VSO: {}, + VSO_PPE: {}, + VSO_DEV: {} + }; + } + } + getClientDetails(callbackUri: Uri): ClientDetails { + let details: ClientDetails | undefined; + switch (callbackUri.scheme) { + case 'code-oss': + details = this._config.OSS; + break; + + case 'vscode-insiders': + details = this._config.INSIDERS; + break; + + case 'vscode': + details = this._config.STABLE; + break; + + case 'vscode-exploration': + details = this._config.EXPLORATION; + break; + + case 'https': + switch (callbackUri.authority) { + case 'online.visualstudio.com': + details = this._config.VSO; + break; + case 'online-ppe.core.vsengsaas.visualstudio.com': + details = this._config.VSO_PPE; + break; + case 'online.dev.core.vsengsaas.visualstudio.com': + details = this._config.VSO_DEV; + break; + } + + default: + throw new Error(`Unrecognized callback ${callbackUri}`); + } + + if (!details.id || !details.secret) { + throw new Error(`No client configuration available for ${callbackUri}`); + } + + return details; + } +} + +const ClientRegistrar = new Registrar(); +export default ClientRegistrar; diff --git a/extensions/github-authentication/src/common/keychain.ts b/extensions/github-authentication/src/common/keychain.ts new file mode 100644 index 0000000000..27c30b19b3 --- /dev/null +++ b/extensions/github-authentication/src/common/keychain.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// keytar depends on a native module shipped in vscode, so this is +// how we load it +import * as keytarType from 'keytar'; +import { env } from 'vscode'; +import Logger from './logger'; + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; + +const SERVICE_ID = `${env.uriScheme}-github.login`; +const ACCOUNT_ID = 'account'; + +export class Keychain { + private keytar: Keytar; + + constructor() { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + } + + async setToken(token: string): Promise { + try { + return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); + } catch (e) { + // Ignore + Logger.error(`Setting token failed: ${e}`); + } + } + + async getToken(): Promise { + try { + return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Getting token failed: ${e}`); + return Promise.resolve(undefined); + } + } + + async deleteToken(): Promise { + try { + return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Deleting token failed: ${e}`); + return Promise.resolve(undefined); + } + } +} + +export const keychain = new Keychain(); diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts new file mode 100644 index 0000000000..9b94569210 --- /dev/null +++ b/extensions/github-authentication/src/common/logger.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +type LogLevel = 'Trace' | 'Info' | 'Error'; + +class Log { + private output: vscode.OutputChannel; + + constructor() { + this.output = vscode.window.createOutputChannel('GitHub Authentication'); + } + + private data2String(data: any): string { + if (data instanceof Error) { + return data.stack || data.message; + } + if (data.success === false && data.message) { + return data.message; + } + return data.toString(); + } + + public info(message: string, data?: any): void { + this.logLevel('Info', message, data); + } + + public error(message: string, data?: any): void { + this.logLevel('Error', message, data); + } + + public logLevel(level: LogLevel, message: string, data?: any): void { + this.output.appendLine(`[${level} - ${this.now()}] ${message}`); + if (data) { + this.output.appendLine(this.data2String(data)); + } + } + + private now(): string { + const now = new Date(); + return padLeft(now.getUTCHours() + '', 2, '0') + + ':' + padLeft(now.getMinutes() + '', 2, '0') + + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); + } +} + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s; +} + +const Logger = new Log(); +export default Logger; diff --git a/extensions/github-authentication/src/common/utils.ts b/extensions/github-authentication/src/common/utils.ts new file mode 100644 index 0000000000..9f19b3abdc --- /dev/null +++ b/extensions/github-authentication/src/common/utils.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Disposable } from 'vscode'; + +export function filterEvent(event: Event, filter: (e: T) => boolean): Event { + return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); +} + +export function onceEvent(event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + const result = event(e => { + result.dispose(); + return listener.call(thisArgs, e); + }, null, disposables); + + return result; + }; +} + + +export interface PromiseAdapter { + ( + value: T, + resolve: + (value?: U | PromiseLike) => void, + reject: + (reason: any) => void + ): any; +} + +const passthrough = (value: any, resolve: (value?: any) => void) => resolve(value); + +/** + * Return a promise that resolves with the next emitted event, or with some future + * event as decided by an adapter. + * + * If specified, the adapter is a function that will be called with + * `(event, resolve, reject)`. It will be called once per event until it resolves or + * rejects. + * + * The default adapter is the passthrough function `(value, resolve) => resolve(value)`. + * + * @param event the event + * @param adapter controls resolution of the returned promise + * @returns a promise that resolves or rejects as specified by the adapter + */ +export async function promiseFromEvent( + event: Event, + adapter: PromiseAdapter = passthrough): Promise { + let subscription: Disposable; + return new Promise((resolve, reject) => + subscription = event((value: T) => { + try { + Promise.resolve(adapter(value, resolve, reject)) + .catch(reject); + } catch (error) { + reject(error); + } + }) + ).then( + (result: U) => { + subscription.dispose(); + return result; + }, + error => { + subscription.dispose(); + throw error; + } + ); +} diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts new file mode 100644 index 0000000000..ba7e99a7cf --- /dev/null +++ b/extensions/github-authentication/src/extension.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { GitHubAuthenticationProvider, onDidChangeSessions } from './github'; +import { uriHandler } from './githubServer'; +import Logger from './common/logger'; + +export async function activate(context: vscode.ExtensionContext) { + + context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); + const loginService = new GitHubAuthenticationProvider(); + + await loginService.initialize(); + + vscode.authentication.registerAuthenticationProvider({ + id: 'GitHub', + displayName: 'GitHub', + onDidChangeSessions: onDidChangeSessions.event, + getSessions: () => Promise.resolve(loginService.sessions), + login: async (scopes: string[]) => { + try { + const session = await loginService.login(scopes.join(' ')); + Logger.info('Login success!'); + return session; + } catch (e) { + vscode.window.showErrorMessage(`Sign in failed: ${e}`); + Logger.error(e); + throw e; + } + }, + logout: async (id: string) => { + return loginService.logout(id); + } + }); + + return; +} + +// this method is called when your extension is deactivated +export function deactivate() { } diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts new file mode 100644 index 0000000000..ce0a1b518a --- /dev/null +++ b/extensions/github-authentication/src/github.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { keychain } from './common/keychain'; +import { GitHubServer } from './githubServer'; +import Logger from './common/logger'; + +export const onDidChangeSessions = new vscode.EventEmitter(); + +export class GitHubAuthenticationProvider { + private _sessions: vscode.AuthenticationSession[] = []; + private _githubServer = new GitHubServer(); + + public async initialize(): Promise { + this._sessions = await this.readSessions(); + this.pollForChange(); + } + + private pollForChange() { + setTimeout(async () => { + const storedSessions = await this.readSessions(); + let didChange = false; + + storedSessions.forEach(session => { + const matchesExisting = this._sessions.some(s => s.id === session.id); + // Another window added a session to the keychain, add it to our state as well + if (!matchesExisting) { + this._sessions.push(session); + didChange = true; + } + }); + + this._sessions.map(session => { + const matchesExisting = storedSessions.some(s => s.id === session.id); + // Another window has logged out, remove from our state + if (!matchesExisting) { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1); + } + + didChange = true; + } + }); + + if (didChange) { + onDidChangeSessions.fire(); + } + + this.pollForChange(); + }, 1000 * 30); + } + + private async readSessions(): Promise { + const storedSessions = await keychain.getToken(); + if (storedSessions) { + try { + return JSON.parse(storedSessions); + } catch (e) { + Logger.error(`Error reading sessions: ${e}`); + } + } + + return []; + } + + private async storeSessions(): Promise { + await keychain.setToken(JSON.stringify(this._sessions)); + } + + get sessions(): vscode.AuthenticationSession[] { + return this._sessions; + } + + public async login(scopes: string): Promise { + const token = await this._githubServer.login(scopes); + const session = await this.tokenToSession(token, scopes.split(' ')); + await this.setToken(session); + return session; + } + + private async tokenToSession(token: string, scopes: string[]): Promise { + const userInfo = await this._githubServer.getUserInfo(token); + return { + id: userInfo.id, + accessToken: () => Promise.resolve(token), + accountName: userInfo.accountName, + scopes: scopes + }; + } + private async setToken(session: vscode.AuthenticationSession): Promise { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1, session); + } else { + this._sessions.push(session); + } + + this.storeSessions(); + } + + public async logout(id: string) { + const sessionIndex = this._sessions.findIndex(session => session.id === id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1); + } + + this.storeSessions(); + } +} diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts new file mode 100644 index 0000000000..f395a1a3e7 --- /dev/null +++ b/extensions/github-authentication/src/githubServer.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as https from 'https'; +import * as vscode from 'vscode'; +import * as uuid from 'uuid'; +import { PromiseAdapter, promiseFromEvent } from './common/utils'; +import Logger from './common/logger'; +import ClientRegistrar, { ClientDetails } from './common/clientRegistrar'; + +class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } +} + +export const uriHandler = new UriEventHandler; + +const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter = + (state, clientDetails) => async (uri, resolve, reject) => { + Logger.info('Exchanging code for token...'); + const query = parseQuery(uri); + const code = query.code; + + if (query.state !== state) { + reject('Received mismatched state'); + return; + } + + const post = https.request({ + host: 'github.com', + path: `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${query.state}&code=${code}`, + method: 'POST', + headers: { + Accept: 'application/json' + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + Logger.info('Token exchange success!'); + resolve(json.access_token); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }; + +function parseQuery(uri: vscode.Uri) { + return uri.query.split('&').reduce((prev: any, current) => { + const queryString = current.split('='); + prev[queryString[0]] = queryString[1]; + return prev; + }, {}); +} + +export class GitHubServer { + public async login(scopes: string): Promise { + Logger.info('Logging in...'); + const state = uuid(); + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const clientDetails = ClientRegistrar.getClientDetails(callbackUri); + const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`); + + vscode.env.openExternal(uri); + return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails)); + } + + public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> { + return new Promise((resolve, reject) => { + Logger.info('Getting account info...'); + const post = https.request({ + host: 'api.github.com', + path: `/user`, + method: 'GET', + headers: { + Authorization: `token ${token}`, + 'User-Agent': 'Visual-Studio-Code' + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + Logger.info('Got account info!'); + resolve({ id: json.id, accountName: json.login }); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } +} diff --git a/extensions/github-authentication/src/typings/ref.d.ts b/extensions/github-authentication/src/typings/ref.d.ts new file mode 100644 index 0000000000..c82a621bfa --- /dev/null +++ b/extensions/github-authentication/src/typings/ref.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json new file mode 100644 index 0000000000..1225709307 --- /dev/null +++ b/extensions/github-authentication/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../shared.tsconfig.json", + "compilerOptions": { + "outDir": "./out", + "experimentalDecorators": true, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock new file mode 100644 index 0000000000..dcab74a1fe --- /dev/null +++ b/extensions/github-authentication/yarn.lock @@ -0,0 +1,454 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/keytar@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" + integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw== + dependencies: + keytar "*" + +"@types/node@^10.12.21": + version "10.17.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541" + integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw== + +"@types/uuid@^3.4.6": + version "3.4.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.7.tgz#51d42247473bc00e38cc8dfaf70d936842a36c03" + integrity sha512-C2j2FWgQkF1ru12SjZJyMaTPxs/f6n90+5G5qNakBxKXjTBc/YTSelHh4Pz1HUDwxFXD9WvpQhOGCDC+/Y4mIQ== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + +chownr@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +keytar@*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.1.0.tgz#d572ed9250ff2b4c8d729621397e00b17bfa5581" + integrity sha512-SptCrRDqLbTeOMB2Z9UmVOS+OKguIrMft+EUaCB8xJPiFMjy6Jnmjgv/LA0rg1ENgLelzwSsC5PSQXF0uoqNDQ== + dependencies: + nan "2.14.0" + prebuild-install "5.3.3" + +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +nan@2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +napi-build-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" + integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== + +node-abi@^2.7.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.14.0.tgz#24650e24e8ffad2b61352519263f0cf4e2ddbfe9" + integrity sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg== + dependencies: + semver "^5.4.1" + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +npmlog@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +prebuild-install@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" + integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.1, readable-stream@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" + integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/extensions/vscode-account/media/auth.css b/extensions/vscode-account/media/auth.css index 09f75d9883..2e2f044820 100644 --- a/extensions/vscode-account/media/auth.css +++ b/extensions/vscode-account/media/auth.css @@ -16,14 +16,14 @@ body { flex-direction: column; color: white; font-family: "Segoe UI","Helvetica Neue","Helvetica",Arial,sans-serif; - background-color: #373277; + background-color: #2C2C32; } .branding { - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+"); + background-image: url('data:image/png;base64,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'); background-size: 24px; background-repeat: no-repeat; - background-position: left 50%; + background-position: left center; padding-left: 36px; font-size: 20px; letter-spacing: -0.04rem; @@ -42,7 +42,7 @@ body { .message { font-weight: 300; - font-size: 1.3rem; + font-size: 1.4rem; } body.error .message { diff --git a/package.json b/package.json index 4a180c28ee..08deb5ffac 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "copy-webpack-plugin": "^4.5.2", "coveralls": "^2.11.11", "cson-parser": "^1.3.3", + "css-loader": "^3.2.0", "debounce": "^1.0.0", "electron": "7.1.11", "eslint": "6.8.0", @@ -126,6 +127,7 @@ "event-stream": "3.3.4", "fancy-log": "^1.3.3", "fast-plist": "0.1.2", + "file-loader": "^4.2.0", "glob": "^5.0.13", "gulp": "^4.0.0", "gulp-atom-electron": "^1.22.0", @@ -173,6 +175,7 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "temp-write": "^3.4.0", + "style-loader": "^1.0.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", "typescript": "3.8.2", @@ -180,7 +183,7 @@ "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.37.0", + "vscode-debugprotocol": "1.39.0-pre.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", diff --git a/src/main.js b/src/main.js index 36c4cb378a..292b3979b5 100644 --- a/src/main.js +++ b/src/main.js @@ -143,7 +143,7 @@ function configureCommandlineSwitchesSync(cliArgs) { // override for the color profile to use 'force-color-profile' ]; - + if (process.platform === 'linux') { SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); } @@ -351,7 +351,7 @@ function setCurrentWorkingDirectory() { function registerListeners() { /** - * Mac: when someone drops a file to the not-yet running VSCode, the open-file event fires even before + * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. * * @type {string[]} @@ -363,7 +363,7 @@ function registerListeners() { }); /** - * React to open-url requests. + * macOS: react to open-url requests. * * @type {string[]} */ diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index fee95cb4c7..a8536d73d4 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -90,9 +90,9 @@ export class Gesture extends Disposable { this.targets = []; this.ignoreTargets = []; this._lastSetTapCountTime = 0; - this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e))); + this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false })); this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e))); - this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e))); + this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false })); } public static addTarget(element: HTMLElement): IDisposable { diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 6173bfe635..dc5d8a6302 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -93,7 +93,6 @@ .monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message-detail { line-height: 22px; flex: 1; /* let the message always grow */ - opacity: .9; } .monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message a:focus { diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 30839e9ed6..b3513af538 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -267,7 +267,13 @@ export class Dialog extends Disposable { if (this.checkbox) { this.checkbox.style(style); } + + if (this.messageDetailElement) { + const messageDetailColor = Color.fromHex(fgColor).transparent(.9); + this.messageDetailElement.style.color = messageDetailColor.makeOpaque(Color.fromHex(bgColor)).toString(); + } } + } } diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 0fca7c7cab..07675932e2 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -125,6 +125,19 @@ export module Iterator { }; } + export function some(iterator: Iterator | NativeIterator, fn: (t: T) => boolean): boolean { + while (true) { + const element = iterator.next(); + if (element.done) { + return false; + } + + if (fn(element.value)) { + return true; + } + } + } + export function forEach(iterator: Iterator, fn: (t: T) => void): void { for (let next = iterator.next(); !next.done; next = iterator.next()) { fn(next.value); diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 499c75b28c..732bc0770f 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -142,10 +142,6 @@ export function decode(buffer: Buffer, encoding: string): string { return iconv.decode(buffer, toNodeEncoding(encoding)); } -export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer { - return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options); -} - export function encodingExists(encoding: string): boolean { return iconv.encodingExists(toNodeEncoding(encoding)); } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 0d5664d9ef..f203219f10 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -14,7 +14,6 @@ import { promisify } from 'util'; import { isRootOrDriveLetter } from 'vs/base/common/extpath'; import { generateUuid } from 'vs/base/common/uuid'; import { normalizeNFC } from 'vs/base/common/normalization'; -import { encode } from 'vs/base/node/encoding'; // See https://github.com/Microsoft/vscode/issues/30180 const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB @@ -320,10 +319,6 @@ function ensureWriteFileQueue(queueKey: string): Queue { export interface IWriteFileOptions { mode?: number; flag?: string; - encoding?: { - charset: string; - addBOM: boolean; - }; } interface IEnsuredWriteFileOptions extends IWriteFileOptions { @@ -339,10 +334,6 @@ let canFlush = true; // // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194 function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void { - if (options.encoding) { - data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM }); - } - if (!canFlush) { return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback); } @@ -378,10 +369,6 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void { const ensuredOptions = ensureWriteOptions(options); - if (ensuredOptions.encoding) { - data = encode(data, ensuredOptions.encoding.charset, { addBOM: ensuredOptions.encoding.addBOM }); - } - if (!canFlush) { return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag }); } @@ -413,8 +400,7 @@ function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptio return { mode: typeof options.mode === 'number' ? options.mode : 0o666, - flag: typeof options.flag === 'string' ? options.flag : 'w', - encoding: options.encoding + flag: typeof options.flag === 'string' ? options.flag : 'w' }; } diff --git a/src/vs/base/node/stream.ts b/src/vs/base/node/stream.ts index 3693165c30..fc99a9b5c4 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -3,75 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer'; import { Readable } from 'stream'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding'; -/** - * Reads a file until a matching string is found. - * - * @param file The file to read. - * @param matchingString The string to search for. - * @param chunkBytes The number of bytes to read each iteration. - * @param maximumBytesToRead The maximum number of bytes to read before giving up. - * @param callback The finished callback. - */ -export function readToMatchingString(file: string, matchingString: string, chunkBytes: number, maximumBytesToRead: number): Promise { - return new Promise((resolve, reject) => - fs.open(file, 'r', null, (err, fd) => { - if (err) { - return reject(err); - } - - function end(err: Error | null, result: string | null): void { - fs.close(fd, closeError => { - if (closeError) { - return reject(closeError); - } - - if (err && (err).code === 'EISDIR') { - return reject(err); // we want to bubble this error up (file is actually a folder) - } - - return resolve(result); - }); - } - - const buffer = Buffer.allocUnsafe(maximumBytesToRead); - let offset = 0; - - function readChunk(): void { - fs.read(fd, buffer, offset, chunkBytes, null, (err, bytesRead) => { - if (err) { - return end(err, null); - } - - if (bytesRead === 0) { - return end(null, null); - } - - offset += bytesRead; - - const newLineIndex = buffer.indexOf(matchingString); - if (newLineIndex >= 0) { - return end(null, buffer.toString('utf8').substr(0, newLineIndex)); - } - - if (offset >= maximumBytesToRead) { - return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null); - } - - return readChunk(); - }); - } - - readChunk(); - }) - ); -} - export function streamToNodeReadable(stream: VSBufferReadableStream): Readable { return new class extends Readable { private listening = false; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1566972f1e..2fa3645cb2 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; +import { IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -73,10 +73,10 @@ import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsSer import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; -import { assign } from 'vs/base/common/objects'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { withNullAsUndefined } from 'vs/base/common/types'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; +import { coalesce } from 'vs/base/common/arrays'; export class CodeApplication extends Disposable { @@ -395,7 +395,7 @@ export class CodeApplication extends Disposable { const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(this.afterWindowOpen.bind(this)); + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentService.args.trace) { @@ -575,9 +575,8 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); - const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); - // ExtensionHost Debug broadcast service + const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService)); // Signal phase: ready (services set) @@ -586,47 +585,67 @@ export class CodeApplication extends Disposable { // Propagate to clients this.dialogMainService = accessor.get(IDialogMainService); - // Create a URL handler to open file URIs in the active window + // Check for initial URLs to handle from protocol link invocations const environmentService = accessor.get(IEnvironmentService); + const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; + const pendingProtocolLinksToHandle = coalesce([ + + // Windows/Linux: protocol handler invokes CLI with --open-url + ...environmentService.args['open-url'] ? environmentService.args._urls || [] : [], + + // macOS: open-url events + ...((global).getOpenUrls() || []) as string[] + ].map(pendingUrlToHandle => { + try { + return URI.parse(pendingUrlToHandle); + } catch (error) { + return undefined; + } + })).filter(pendingUriToHandle => { + // filter out any protocol link that wants to open as window so that + // we open the right set of windows on startup and not restore the + // previous workspace too. + const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle); + if (windowOpenable) { + pendingWindowOpenablesFromProtocolLinks.push(windowOpenable); + + return false; + } + + return true; + }); + + // Create a URL handler to open file URIs in the active window + const app = this; urlService.registerHandler({ - async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + async handleURL(uri: URI): Promise { - // Catch file/remote URLs - if ((uri.authority === Schemas.file || uri.authority === Schemas.vscodeRemote) && !!uri.path) { - const cli = assign(Object.create(null), environmentService.args); - const urisToOpen: IWindowOpenable[] = []; + // Check for URIs to open in window + const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri); + if (windowOpenableFromProtocolLink) { + windowsMainService.open({ + context: OpenContext.API, + cli: { ...environmentService.args }, + urisToOpen: [windowOpenableFromProtocolLink], + gotoLineMode: true + }); - // File path - if (uri.authority === Schemas.file) { - // we configure as fileUri, but later validation will - // make sure to open as folder or workspace if possible - urisToOpen.push({ fileUri: URI.file(uri.fsPath) }); - } + return true; + } - // Remote path - else { - // Example conversion: - // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco - // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco - const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); - if (secondSlash !== -1) { - const authority = uri.path.substring(1, secondSlash); - const path = uri.path.substring(secondSlash); - const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); + // If we have not yet handled the URI and we have no window opened (macOS only) + // we first open a window and then try to open that URI within that window + if (isMacintosh && windowsMainService.getWindowCount() === 0) { + const [window] = windowsMainService.open({ + context: OpenContext.API, + cli: { ...environmentService.args }, + forceEmpty: true, + gotoLineMode: true + }); - if (hasWorkspaceFileExtension(path)) { - urisToOpen.push({ workspaceUri: remoteUri }); - } else { - urisToOpen.push({ folderUri: remoteUri }); - } - } - } + await window.ready(); - if (urisToOpen.length > 0) { - windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); - - return true; - } + return urlService.open(uri); } return false; @@ -638,37 +657,13 @@ export class CodeApplication extends Disposable { const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); - const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel); - - // On Mac, Code can be running without any open windows, so we must create a window to handle urls, - // if there is none - if (isMacintosh) { - urlService.registerHandler({ - async handleURL(uri: URI, options?: IOpenURLOptions): Promise { - if (windowsMainService.getWindowCount() === 0) { - const cli = { ...environmentService.args }; - const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true }); - - await window.ready(); - - return urlService.open(uri); - } - - return false; - } - }); - } - - // Register the multiple URL handler - urlService.registerHandler(multiplexURLHandler); + urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); // Watch Electron URLs and forward them to the UrlService - const args = this.environmentService.args; - const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService, this.environmentService); - this._register(urlListener); + this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService)); // Open our first window + const args = this.environmentService.args; const macOpenFiles: string[] = (global).macOpenFiles; const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; const hasCliArgs = args._.length; @@ -677,6 +672,19 @@ export class CodeApplication extends Disposable { const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; + // check for a pending window to open from URI + // e.g. when running code with --open-uri from + // a protocol handler + if (pendingWindowOpenablesFromProtocolLinks.length > 0) { + return windowsMainService.open({ + context, + cli: args, + urisToOpen: pendingWindowOpenablesFromProtocolLinks, + gotoLineMode: true, + initialStartup: true + }); + } + // new window if "-n" or "--remote" was used without paths if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { return windowsMainService.open({ @@ -698,7 +706,6 @@ export class CodeApplication extends Disposable { urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)), noRecentEntry, waitMarkerFileURI, - gotoLineMode: false, initialStartup: true }); } @@ -716,6 +723,40 @@ export class CodeApplication extends Disposable { }); } + private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined { + if (!uri.path) { + return undefined; + } + + // File path + if (uri.authority === Schemas.file) { + // we configure as fileUri, but later validation will + // make sure to open as folder or workspace if possible + return { fileUri: URI.file(uri.fsPath) }; + } + + // Remote path + else if (uri.authority === Schemas.vscodeRemote) { + // Example conversion: + // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco + // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco + const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); + if (secondSlash !== -1) { + const authority = uri.path.substring(1, secondSlash); + const path = uri.path.substring(secondSlash); + const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); + + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: remoteUri }; + } else { + return { folderUri: remoteUri }; + } + } + } + + return undefined; + } + private getWindowOpenableFromPathSync(path: string): IWindowOpenable { try { const fileStat = statSync(path); @@ -734,6 +775,7 @@ export class CodeApplication extends Disposable { } private afterWindowOpen(accessor: ServicesAccessor): void { + // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; @@ -763,7 +805,7 @@ class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHost super(); } - call(ctx: TContext, command: string, arg?: any): Promise { + async call(ctx: TContext, command: string, arg?: any): Promise { if (command === 'openExtensionDevelopmentHostWindow') { const env = arg[1]; const pargs = parseArgs(arg[0], OPTIONS); @@ -775,7 +817,6 @@ class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHost userEnv: Object.keys(env).length > 0 ? env : undefined }); } - return Promise.resolve(); } else { return super.call(ctx, command, arg); } diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts index b1084eb02d..fc1a6ea8b3 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -19,8 +19,8 @@ export function validatePaths(args: ParsedArgs): ParsedArgs { args._ = []; } + // Normalize paths and watch out for goto line mode if (!args['remote']) { - // Normalize paths and watch out for goto line mode const paths = doValidatePaths(args._, args.goto); args._ = paths; } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index c76e4fee40..ea84fa8f9e 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -369,7 +369,7 @@ class Widget { return { fitsAbove, - aboveTop: Math.max(aboveTop, TOP_PADDING), + aboveTop: aboveTop, aboveLeft, fitsBelow, belowTop, diff --git a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts index 03673d4b61..0349889c4a 100644 --- a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts +++ b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts @@ -121,6 +121,13 @@ export class RangeUtil { startChildIndex = Math.min(max, Math.max(min, startChildIndex)); endChildIndex = Math.min(max, Math.max(min, endChildIndex)); + if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0) { + // We must find the position at the beginning of a + // To cover cases of empty s, aboid using a range and use the 's bounding box + const clientRects = domNode.children[startChildIndex].getClientRects(); + return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft); + } + // If crossing over to a span only to select offset 0, then use the previous span's maximum offset // Chrome is buggy and doesn't handle 0 offsets well sometimes. if (startChildIndex !== endChildIndex) { diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 285dec33ff..0fbd8d8038 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -195,7 +195,7 @@ export class ViewLine implements IVisibleLine { const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); if (startColumn < endColumn) { - if (this._options.renderWhitespace !== 'selection') { + if (options.themeType === HIGH_CONTRAST || this._options.renderWhitespace !== 'selection') { actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular)); } else { if (!selectionsOnLine) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 2b719ca999..f33577eb80 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1149,15 +1149,15 @@ class InnerMinimap extends Disposable { this._gestureInProgress = true; this.scrollDueToTouchEvent(e); } - }); + }, { passive: false }); - this._sliderTouchMoveListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => { + this._sliderTouchMoveListener = dom.addDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => { e.preventDefault(); e.stopPropagation(); if (this._lastRenderData && this._gestureInProgress) { this.scrollDueToTouchEvent(e); } - }); + }, { passive: false }); this._sliderTouchEndListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.End, (e: GestureEvent) => { e.preventDefault(); diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index eecc1b3924..aaf2a0e3ff 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -523,7 +523,7 @@ const editorConfiguration: IConfigurationNode = { 'diffEditor.ignoreTrimWhitespace': { type: 'boolean', default: true, - description: nls.localize('ignoreTrimWhitespace', "Controls whether the diff editor shows changes in leading or trailing whitespace as diffs.") + description: nls.localize('ignoreTrimWhitespace', "When enabled, the diff editor ignores changes in leading or trailing whitespace.") }, 'diffEditor.renderIndicators': { type: 'boolean', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index e73889de35..0083d06b3a 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -506,6 +506,11 @@ export interface IEditorOptions { * Defaults to 'mouseover'. */ showFoldingControls?: 'always' | 'mouseover'; + /** + * Controls whether clicking on the empty content after a folded line will unfold the line. + * Defaults to false. + */ + unfoldOnClickInEmptyContent?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -3328,6 +3333,7 @@ export const enum EditorOption { folding, foldingStrategy, foldingHighlight, + unfoldOnClickInEmptyContent, fontFamily, fontInfo, fontLigatures, @@ -3627,6 +3633,10 @@ export const EditorOptions = { EditorOption.foldingHighlight, 'foldingHighlight', true, { description: nls.localize('foldingHighlight', "Controls whether the editor should highlight folded ranges.") } )), + unfoldOnClickInEmptyContent: register(new EditorBooleanOption( + EditorOption.unfoldOnClickInEmptyContent, 'unfoldOnClickInEmptyContent', false, + { description: nls.localize('unfoldOnClickInEmptyContent', "Controls whether clicking on the empty content after a folded line will unfold the line.") } + )), fontFamily: register(new EditorStringOption( EditorOption.fontFamily, 'fontFamily', EDITOR_FONT_DEFAULTS.fontFamily, { description: nls.localize('fontFamily', "Controls the font family.") } diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index b82f43d4d4..1df662fe20 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -297,7 +297,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { return this._cursors.getAll(); } - public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): void { + public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean { if (states !== null && states.length > Cursor.MAX_CURSOR_COUNT) { states = states.slice(0, Cursor.MAX_CURSOR_COUNT); this._onDidReachMaxCursorCount.fire(undefined); @@ -311,7 +311,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._validateAutoClosedActions(); - this._emitStateChangedIfNecessary(source, reason, oldState); + return this._emitStateChangedIfNecessary(source, reason, oldState); } public setColumnSelectData(columnSelectData: IColumnSelectData): void { @@ -411,7 +411,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } else { if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) { const cursorState = CursorState.fromModelSelections(e.resultingSelection); - this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState); + if (this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState)) { + this._revealRange('modelChange', RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth); + } } else { const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 37c0ae3f98..86b0451c97 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -199,85 +199,86 @@ export enum EditorOption { folding = 31, foldingStrategy = 32, foldingHighlight = 33, - fontFamily = 34, - fontInfo = 35, - fontLigatures = 36, - fontSize = 37, - fontWeight = 38, - formatOnPaste = 39, - formatOnType = 40, - glyphMargin = 41, - gotoLocation = 42, - hideCursorInOverviewRuler = 43, - highlightActiveIndentGuide = 44, - hover = 45, - inDiffEditor = 46, - letterSpacing = 47, - lightbulb = 48, - lineDecorationsWidth = 49, - lineHeight = 50, - lineNumbers = 51, - lineNumbersMinChars = 52, - links = 53, - matchBrackets = 54, - minimap = 55, - mouseStyle = 56, - mouseWheelScrollSensitivity = 57, - mouseWheelZoom = 58, - multiCursorMergeOverlapping = 59, - multiCursorModifier = 60, - multiCursorPaste = 61, - occurrencesHighlight = 62, - overviewRulerBorder = 63, - overviewRulerLanes = 64, - padding = 65, - parameterHints = 66, - peekWidgetDefaultFocus = 67, - definitionLinkOpensInPeek = 68, - quickSuggestions = 69, - quickSuggestionsDelay = 70, - readOnly = 71, - renderControlCharacters = 72, - renderIndentGuides = 73, - renderFinalNewline = 74, - renderLineHighlight = 75, - renderValidationDecorations = 76, - renderWhitespace = 77, - revealHorizontalRightPadding = 78, - roundedSelection = 79, - rulers = 80, - scrollbar = 81, - scrollBeyondLastColumn = 82, - scrollBeyondLastLine = 83, - scrollPredominantAxis = 84, - selectionClipboard = 85, - selectionHighlight = 86, - selectOnLineNumbers = 87, - showFoldingControls = 88, - showUnused = 89, - snippetSuggestions = 90, - smoothScrolling = 91, - stopRenderingLineAfter = 92, - suggest = 93, - suggestFontSize = 94, - suggestLineHeight = 95, - suggestOnTriggerCharacters = 96, - suggestSelection = 97, - tabCompletion = 98, - useTabStops = 99, - wordSeparators = 100, - wordWrap = 101, - wordWrapBreakAfterCharacters = 102, - wordWrapBreakBeforeCharacters = 103, - wordWrapColumn = 104, - wordWrapMinified = 105, - wrappingIndent = 106, - wrappingStrategy = 107, - editorClassName = 108, - pixelRatio = 109, - tabFocusMode = 110, - layoutInfo = 111, - wrappingInfo = 112 + unfoldOnClickInEmptyContent = 34, + fontFamily = 35, + fontInfo = 36, + fontLigatures = 37, + fontSize = 38, + fontWeight = 39, + formatOnPaste = 40, + formatOnType = 41, + glyphMargin = 42, + gotoLocation = 43, + hideCursorInOverviewRuler = 44, + highlightActiveIndentGuide = 45, + hover = 46, + inDiffEditor = 47, + letterSpacing = 48, + lightbulb = 49, + lineDecorationsWidth = 50, + lineHeight = 51, + lineNumbers = 52, + lineNumbersMinChars = 53, + links = 54, + matchBrackets = 55, + minimap = 56, + mouseStyle = 57, + mouseWheelScrollSensitivity = 58, + mouseWheelZoom = 59, + multiCursorMergeOverlapping = 60, + multiCursorModifier = 61, + multiCursorPaste = 62, + occurrencesHighlight = 63, + overviewRulerBorder = 64, + overviewRulerLanes = 65, + padding = 66, + parameterHints = 67, + peekWidgetDefaultFocus = 68, + definitionLinkOpensInPeek = 69, + quickSuggestions = 70, + quickSuggestionsDelay = 71, + readOnly = 72, + renderControlCharacters = 73, + renderIndentGuides = 74, + renderFinalNewline = 75, + renderLineHighlight = 76, + renderValidationDecorations = 77, + renderWhitespace = 78, + revealHorizontalRightPadding = 79, + roundedSelection = 80, + rulers = 81, + scrollbar = 82, + scrollBeyondLastColumn = 83, + scrollBeyondLastLine = 84, + scrollPredominantAxis = 85, + selectionClipboard = 86, + selectionHighlight = 87, + selectOnLineNumbers = 88, + showFoldingControls = 89, + showUnused = 90, + snippetSuggestions = 91, + smoothScrolling = 92, + stopRenderingLineAfter = 93, + suggest = 94, + suggestFontSize = 95, + suggestLineHeight = 96, + suggestOnTriggerCharacters = 97, + suggestSelection = 98, + tabCompletion = 99, + useTabStops = 100, + wordSeparators = 101, + wordWrap = 102, + wordWrapBreakAfterCharacters = 103, + wordWrapBreakBeforeCharacters = 104, + wordWrapColumn = 105, + wordWrapMinified = 106, + wrappingIndent = 107, + wrappingStrategy = 108, + editorClassName = 109, + pixelRatio = 110, + tabFocusMode = 111, + layoutInfo = 112, + wrappingInfo = 113 } /** diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index e316cc2608..4442d47f47 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -6,6 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { Constants } from 'vs/base/common/uint'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { LinePartMetadata } from 'vs/editor/common/viewLayout/viewLineRenderer'; export class LineDecoration { _lineDecorationBrand: void; @@ -28,8 +29,8 @@ export class LineDecoration { } public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean { - let aLen = a.length; - let bLen = b.length; + const aLen = a.length; + const bLen = b.length; if (aLen !== bLen) { return false; } @@ -49,8 +50,8 @@ export class LineDecoration { let result: LineDecoration[] = [], resultLen = 0; for (let i = 0, len = lineDecorations.length; i < len; i++) { - let d = lineDecorations[i]; - let range = d.range; + const d = lineDecorations[i]; + const range = d.range; if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) { // Ignore decorations that sit outside this line @@ -62,8 +63,8 @@ export class LineDecoration { continue; } - let startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn); - let endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn); + const startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn); + const endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn); result[resultLen++] = new LineDecoration(startColumn, endColumn, d.inlineClassName, d.type); } @@ -71,16 +72,25 @@ export class LineDecoration { return result; } + private static _typeCompare(a: InlineDecorationType, b: InlineDecorationType): number { + const ORDER = [2, 0, 1, 3]; + return ORDER[a] - ORDER[b]; + } + public static compare(a: LineDecoration, b: LineDecoration): number { if (a.startColumn === b.startColumn) { if (a.endColumn === b.endColumn) { - if (a.className < b.className) { - return -1; + const typeCmp = LineDecoration._typeCompare(a.type, b.type); + if (typeCmp === 0) { + if (a.className < b.className) { + return -1; + } + if (a.className > b.className) { + return 1; + } + return 0; } - if (a.className > b.className) { - return 1; - } - return 0; + return typeCmp; } return a.endColumn - b.endColumn; } @@ -92,11 +102,13 @@ export class DecorationSegment { startOffset: number; endOffset: number; className: string; + metadata: number; - constructor(startOffset: number, endOffset: number, className: string) { + constructor(startOffset: number, endOffset: number, className: string, metadata: number) { this.startOffset = startOffset; this.endOffset = endOffset; this.className = className; + this.metadata = metadata; } } @@ -104,13 +116,23 @@ class Stack { public count: number; private readonly stopOffsets: number[]; private readonly classNames: string[]; + private readonly metadata: number[]; constructor() { this.stopOffsets = []; this.classNames = []; + this.metadata = []; this.count = 0; } + private static _metadata(metadata: number[]): number { + let result = 0; + for (let i = 0, len = metadata.length; i < len; i++) { + result |= metadata[i]; + } + return result; + } + public consumeLowerThan(maxStopOffset: number, nextStartOffset: number, result: DecorationSegment[]): number { while (this.count > 0 && this.stopOffsets[0] < maxStopOffset) { @@ -122,34 +144,37 @@ class Stack { } // Basically we are consuming the first i + 1 elements of the stack - result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' '))); + result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' '), Stack._metadata(this.metadata))); nextStartOffset = this.stopOffsets[i] + 1; // Consume them this.stopOffsets.splice(0, i + 1); this.classNames.splice(0, i + 1); + this.metadata.splice(0, i + 1); this.count -= (i + 1); } if (this.count > 0 && nextStartOffset < maxStopOffset) { - result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '))); + result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '), Stack._metadata(this.metadata))); nextStartOffset = maxStopOffset; } return nextStartOffset; } - public insert(stopOffset: number, className: string): void { + public insert(stopOffset: number, className: string, metadata: number): void { if (this.count === 0 || this.stopOffsets[this.count - 1] <= stopOffset) { // Insert at the end this.stopOffsets.push(stopOffset); this.classNames.push(className); + this.metadata.push(metadata); } else { // Find the insertion position for `stopOffset` for (let i = 0; i < this.count; i++) { if (this.stopOffsets[i] >= stopOffset) { this.stopOffsets.splice(i, 0, stopOffset); this.classNames.splice(i, 0, className); + this.metadata.splice(i, 0, metadata); break; } } @@ -170,14 +195,21 @@ export class LineDecorationsNormalizer { let result: DecorationSegment[] = []; - let stack = new Stack(); + const stack = new Stack(); let nextStartOffset = 0; for (let i = 0, len = lineDecorations.length; i < len; i++) { - let d = lineDecorations[i]; + const d = lineDecorations[i]; let startColumn = d.startColumn; let endColumn = d.endColumn; - let className = d.className; + const className = d.className; + const metadata = ( + d.type === InlineDecorationType.Before + ? LinePartMetadata.PSEUDO_BEFORE + : d.type === InlineDecorationType.After + ? LinePartMetadata.PSEUDO_AFTER + : 0 + ); // If the position would end up in the middle of a high-low surrogate pair, we move it to before the pair if (startColumn > 1) { @@ -194,15 +226,15 @@ export class LineDecorationsNormalizer { } } - let currentStartOffset = startColumn - 1; - let currentEndOffset = endColumn - 2; + const currentStartOffset = startColumn - 1; + const currentEndOffset = endColumn - 2; nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result); if (stack.count === 0) { nextStartOffset = currentStartOffset; } - stack.insert(currentEndOffset, className); + stack.insert(currentEndOffset, className, metadata); } stack.consumeLowerThan(Constants.MAX_SAFE_SMALL_INTEGER, nextStartOffset, result); diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 27bba3591f..866fc65dc8 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -17,6 +17,16 @@ export const enum RenderWhitespace { All = 3 } +export const enum LinePartMetadata { + IS_WHITESPACE = 1, + PSEUDO_BEFORE = 2, + PSEUDO_AFTER = 4, + + IS_WHITESPACE_MASK = 0b001, + PSEUDO_BEFORE_MASK = 0b010, + PSEUDO_AFTER_MASK = 0b100, +} + class LinePart { _linePartBrand: void; @@ -25,10 +35,16 @@ class LinePart { */ public readonly endIndex: number; public readonly type: string; + public readonly metadata: number; - constructor(endIndex: number, type: string) { + constructor(endIndex: number, type: string, metadata: number) { this.endIndex = endIndex; this.type = type; + this.metadata = metadata; + } + + public isWhitespace(): boolean { + return (this.metadata & LinePartMetadata.IS_WHITESPACE_MASK ? true : false); } } @@ -470,7 +486,7 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength // The faux indent part of the line should have no token type if (fauxIndentLength > 0) { - result[resultLen++] = new LinePart(fauxIndentLength, ''); + result[resultLen++] = new LinePart(fauxIndentLength, '', 0); } for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) { @@ -481,10 +497,10 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength } const type = tokens.getClassName(tokenIndex); if (endIndex >= len) { - result[resultLen++] = new LinePart(len, type); + result[resultLen++] = new LinePart(len, type, 0); break; } - result[resultLen++] = new LinePart(endIndex, type); + result[resultLen++] = new LinePart(endIndex, type, 0); } return result; @@ -513,6 +529,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: const tokenEndIndex = token.endIndex; if (lastTokenEndIndex + Constants.LongToken < tokenEndIndex) { const tokenType = token.type; + const tokenMetadata = token.metadata; let lastSpaceOffset = -1; let currTokenStart = lastTokenEndIndex; @@ -522,13 +539,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: } if (lastSpaceOffset !== -1 && j - currTokenStart >= Constants.LongToken) { // Split at `lastSpaceOffset` + 1 - result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType); + result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata); currTokenStart = lastSpaceOffset + 1; lastSpaceOffset = -1; } } if (currTokenStart !== tokenEndIndex) { - result[resultLen++] = new LinePart(tokenEndIndex, tokenType); + result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata); } } else { result[resultLen++] = token; @@ -544,12 +561,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: let diff = (tokenEndIndex - lastTokenEndIndex); if (diff > Constants.LongToken) { const tokenType = token.type; + const tokenMetadata = token.metadata; const piecesCount = Math.ceil(diff / Constants.LongToken); for (let j = 1; j < piecesCount; j++) { let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken); - result[resultLen++] = new LinePart(pieceEndIndex, tokenType); + result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata); } - result[resultLen++] = new LinePart(tokenEndIndex, tokenType); + result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata); } else { result[resultLen++] = token; } @@ -640,17 +658,17 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len if (generateLinePartForEachWhitespace) { const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); for (let i = lastEndIndex + 1; i <= charIndex; i++) { - result[resultLen++] = new LinePart(i, 'mtkw'); + result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(charIndex, 'mtkw'); + result[resultLen++] = new LinePart(charIndex, 'mtkw', LinePartMetadata.IS_WHITESPACE); } tmpIndent = tmpIndent % tabSize; } } else { // was in regular token if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) { - result[resultLen++] = new LinePart(charIndex, tokenType); + result[resultLen++] = new LinePart(charIndex, tokenType, 0); tmpIndent = tmpIndent % tabSize; } } @@ -693,13 +711,13 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len if (generateLinePartForEachWhitespace) { const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); for (let i = lastEndIndex + 1; i <= len; i++) { - result[resultLen++] = new LinePart(i, 'mtkw'); + result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(len, 'mtkw'); + result[resultLen++] = new LinePart(len, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(len, tokenType); + result[resultLen++] = new LinePart(len, tokenType, 0); } return result; @@ -720,42 +738,45 @@ function _applyInlineDecorations(lineContent: string, len: number, tokens: LineP const token = tokens[tokenIndex]; const tokenEndIndex = token.endIndex; const tokenType = token.type; + const tokenMetadata = token.metadata; while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) { const lineDecoration = lineDecorations[lineDecorationIndex]; if (lineDecoration.startOffset > lastResultEndIndex) { lastResultEndIndex = lineDecoration.startOffset; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata); } if (lineDecoration.endOffset + 1 <= tokenEndIndex) { // This line decoration ends before this token ends lastResultEndIndex = lineDecoration.endOffset + 1; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata); lineDecorationIndex++; } else { // This line decoration continues on to the next token lastResultEndIndex = tokenEndIndex; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata); break; } } if (tokenEndIndex > lastResultEndIndex) { lastResultEndIndex = tokenEndIndex; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata); } } const lastTokenEndIndex = tokens[tokens.length - 1].endIndex; if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) { let classNames: string[] = []; + let metadata = 0; while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) { classNames.push(lineDecorations[lineDecorationIndex].className); + metadata |= lineDecorations[lineDecorationIndex].metadata; lineDecorationIndex++; } - result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' ')); + result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' '), metadata); } return result; @@ -788,6 +809,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render let visibleColumn = startVisibleColumn; let charOffsetInPart = 0; + let partDisplacement = 0; let prevPartContentCnt = 0; let partAbsoluteOffset = 0; @@ -799,8 +821,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const part = parts[partIndex]; const partEndIndex = part.endIndex; const partType = part.type; - const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('mtkw') >= 0)); + const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && part.isWhitespace()); const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw'/*only whitespace*/ || !containsForeignElements); + const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.metadata === LinePartMetadata.PSEUDO_AFTER); charOffsetInPart = 0; sb.appendASCIIString('= 1)) { + const lineContent = model.getLineContent(marker.startLineNumber); + ariaLabel = `${lineContent}, ${ariaLabel}`; + } + return ariaLabel; + } } export class MarkerNavigationWidget extends PeekViewWidget { @@ -316,7 +344,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { } this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`; - this.editor.revealPositionInCenter(position, ScrollType.Smooth); + this.editor.revealPositionNearTop(position, ScrollType.Smooth); this.editor.focus(); } diff --git a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts index 775aa02456..1876d0167d 100644 --- a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts +++ b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts @@ -18,9 +18,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { ]); assert.deepEqual(result, [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c2 c1'), - new DecorationSegment(3, 9, 'c1'), + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c2 c1', 0), + new DecorationSegment(3, 9, 'c1', 0), ]); }); @@ -32,8 +32,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { ]); assert.deepEqual(result, [ - new DecorationSegment(14, 18, 'mtkw'), - new DecorationSegment(19, 19, 'mtkw inline-folded') + new DecorationSegment(14, 18, 'mtkw', 0), + new DecorationSegment(19, 19, 'mtkw inline-folded', 0) ]); }); @@ -66,24 +66,24 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 2, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 0, 'c1'), - new DecorationSegment(2, 2, 'c2') + new DecorationSegment(0, 0, 'c1', 0), + new DecorationSegment(2, 2, 'c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 3, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c2') + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c1 c2') + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c1 c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -91,8 +91,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1*'), - new DecorationSegment(2, 2, 'c1 c1* c2') + new DecorationSegment(0, 1, 'c1 c1*', 0), + new DecorationSegment(2, 2, 'c1 c1* c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -101,8 +101,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -112,8 +112,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2*', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -123,9 +123,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular), new LineDecoration(3, 5, 'c2*', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*'), - new DecorationSegment(3, 3, 'c2*') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0), + new DecorationSegment(3, 3, 'c2*', 0) ]); }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index a74d1c8566..00d816d23c 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -388,6 +388,66 @@ suite('viewLineRenderer.renderLine', () => { assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); }); + test('issue #91178: after decoration type shown before cursor', () => { + const lineText = '//just a comment'; + const lineParts = createViewLineTokens([ + createPart(16, 1) + ]); + const expectedOutput = [ + '//just\u00a0a\u00a0com', + '', + '', + 'ment', + ].join(''); + + const expectedCharacterMapping = new CharacterMapping(17, 4); + expectedCharacterMapping.setPartData(0, 0, 0, 0); + expectedCharacterMapping.setPartData(1, 0, 1, 0); + expectedCharacterMapping.setPartData(2, 0, 2, 0); + expectedCharacterMapping.setPartData(3, 0, 3, 0); + expectedCharacterMapping.setPartData(4, 0, 4, 0); + expectedCharacterMapping.setPartData(5, 0, 5, 0); + expectedCharacterMapping.setPartData(6, 0, 6, 0); + expectedCharacterMapping.setPartData(7, 0, 7, 0); + expectedCharacterMapping.setPartData(8, 0, 8, 0); + expectedCharacterMapping.setPartData(9, 0, 9, 0); + expectedCharacterMapping.setPartData(10, 0, 10, 0); + expectedCharacterMapping.setPartData(11, 0, 11, 0); + expectedCharacterMapping.setPartData(12, 2, 0, 12); + expectedCharacterMapping.setPartData(13, 3, 1, 12); + expectedCharacterMapping.setPartData(14, 3, 2, 12); + expectedCharacterMapping.setPartData(15, 3, 3, 12); + expectedCharacterMapping.setPartData(16, 3, 4, 12); + + const actual = renderViewLine(new RenderLineInput( + true, + false, + lineText, + false, + true, + false, + 0, + lineParts, + [ + new LineDecoration(13, 13, 'dec1', InlineDecorationType.After), + new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before), + ], + 4, + 0, + 10, + 10, + 10, + -1, + 'none', + false, + false, + null + )); + + assert.equal(actual.html, '' + expectedOutput + ''); + assertCharacterMapping2(actual.characterMapping, expectedCharacterMapping); + }); + test('issue Microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => { let lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; @@ -693,6 +753,33 @@ suite('viewLineRenderer.renderLine', () => { assert.equal(_actual.html, '' + expectedOutput + ''); }); + interface ICharMappingData { + charOffset: number; + partIndex: number; + charIndex: number; + } + + function decodeCharacterMapping(source: CharacterMapping) { + const mapping: ICharMappingData[] = []; + for (let charOffset = 0; charOffset < source.length; charOffset++) { + const partData = source.charOffsetToPartData(charOffset); + const partIndex = CharacterMapping.getPartIndex(partData); + const charIndex = CharacterMapping.getCharIndex(partData); + mapping.push({ charOffset, partIndex, charIndex }); + } + const absoluteOffsets: number[] = []; + for (const absoluteOffset of source.getAbsoluteOffsets()) { + absoluteOffsets.push(absoluteOffset); + } + return { mapping, absoluteOffsets }; + } + + function assertCharacterMapping2(actual: CharacterMapping, expected: CharacterMapping): void { + const _actual = decodeCharacterMapping(actual); + const _expected = decodeCharacterMapping(expected); + assert.deepEqual(_actual, _expected); + } + function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void { assertCharPartOffsets(actual, expectedCharPartOffsets); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 832a37e3a9..d1c3fa64f9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3031,6 +3031,11 @@ declare namespace monaco.editor { * Defaults to 'mouseover'. */ showFoldingControls?: 'always' | 'mouseover'; + /** + * Controls whether clicking on the empty content after a folded line will unfold the line. + * Defaults to false. + */ + unfoldOnClickInEmptyContent?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -3824,85 +3829,86 @@ declare namespace monaco.editor { folding = 31, foldingStrategy = 32, foldingHighlight = 33, - fontFamily = 34, - fontInfo = 35, - fontLigatures = 36, - fontSize = 37, - fontWeight = 38, - formatOnPaste = 39, - formatOnType = 40, - glyphMargin = 41, - gotoLocation = 42, - hideCursorInOverviewRuler = 43, - highlightActiveIndentGuide = 44, - hover = 45, - inDiffEditor = 46, - letterSpacing = 47, - lightbulb = 48, - lineDecorationsWidth = 49, - lineHeight = 50, - lineNumbers = 51, - lineNumbersMinChars = 52, - links = 53, - matchBrackets = 54, - minimap = 55, - mouseStyle = 56, - mouseWheelScrollSensitivity = 57, - mouseWheelZoom = 58, - multiCursorMergeOverlapping = 59, - multiCursorModifier = 60, - multiCursorPaste = 61, - occurrencesHighlight = 62, - overviewRulerBorder = 63, - overviewRulerLanes = 64, - padding = 65, - parameterHints = 66, - peekWidgetDefaultFocus = 67, - definitionLinkOpensInPeek = 68, - quickSuggestions = 69, - quickSuggestionsDelay = 70, - readOnly = 71, - renderControlCharacters = 72, - renderIndentGuides = 73, - renderFinalNewline = 74, - renderLineHighlight = 75, - renderValidationDecorations = 76, - renderWhitespace = 77, - revealHorizontalRightPadding = 78, - roundedSelection = 79, - rulers = 80, - scrollbar = 81, - scrollBeyondLastColumn = 82, - scrollBeyondLastLine = 83, - scrollPredominantAxis = 84, - selectionClipboard = 85, - selectionHighlight = 86, - selectOnLineNumbers = 87, - showFoldingControls = 88, - showUnused = 89, - snippetSuggestions = 90, - smoothScrolling = 91, - stopRenderingLineAfter = 92, - suggest = 93, - suggestFontSize = 94, - suggestLineHeight = 95, - suggestOnTriggerCharacters = 96, - suggestSelection = 97, - tabCompletion = 98, - useTabStops = 99, - wordSeparators = 100, - wordWrap = 101, - wordWrapBreakAfterCharacters = 102, - wordWrapBreakBeforeCharacters = 103, - wordWrapColumn = 104, - wordWrapMinified = 105, - wrappingIndent = 106, - wrappingStrategy = 107, - editorClassName = 108, - pixelRatio = 109, - tabFocusMode = 110, - layoutInfo = 111, - wrappingInfo = 112 + unfoldOnClickInEmptyContent = 34, + fontFamily = 35, + fontInfo = 36, + fontLigatures = 37, + fontSize = 38, + fontWeight = 39, + formatOnPaste = 40, + formatOnType = 41, + glyphMargin = 42, + gotoLocation = 43, + hideCursorInOverviewRuler = 44, + highlightActiveIndentGuide = 45, + hover = 46, + inDiffEditor = 47, + letterSpacing = 48, + lightbulb = 49, + lineDecorationsWidth = 50, + lineHeight = 51, + lineNumbers = 52, + lineNumbersMinChars = 53, + links = 54, + matchBrackets = 55, + minimap = 56, + mouseStyle = 57, + mouseWheelScrollSensitivity = 58, + mouseWheelZoom = 59, + multiCursorMergeOverlapping = 60, + multiCursorModifier = 61, + multiCursorPaste = 62, + occurrencesHighlight = 63, + overviewRulerBorder = 64, + overviewRulerLanes = 65, + padding = 66, + parameterHints = 67, + peekWidgetDefaultFocus = 68, + definitionLinkOpensInPeek = 69, + quickSuggestions = 70, + quickSuggestionsDelay = 71, + readOnly = 72, + renderControlCharacters = 73, + renderIndentGuides = 74, + renderFinalNewline = 75, + renderLineHighlight = 76, + renderValidationDecorations = 77, + renderWhitespace = 78, + revealHorizontalRightPadding = 79, + roundedSelection = 80, + rulers = 81, + scrollbar = 82, + scrollBeyondLastColumn = 83, + scrollBeyondLastLine = 84, + scrollPredominantAxis = 85, + selectionClipboard = 86, + selectionHighlight = 87, + selectOnLineNumbers = 88, + showFoldingControls = 89, + showUnused = 90, + snippetSuggestions = 91, + smoothScrolling = 92, + stopRenderingLineAfter = 93, + suggest = 94, + suggestFontSize = 95, + suggestLineHeight = 96, + suggestOnTriggerCharacters = 97, + suggestSelection = 98, + tabCompletion = 99, + useTabStops = 100, + wordSeparators = 101, + wordWrap = 102, + wordWrapBreakAfterCharacters = 103, + wordWrapBreakBeforeCharacters = 104, + wordWrapColumn = 105, + wordWrapMinified = 106, + wrappingIndent = 107, + wrappingStrategy = 108, + editorClassName = 109, + pixelRatio = 110, + tabFocusMode = 111, + layoutInfo = 112, + wrappingInfo = 113 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3939,6 +3945,7 @@ declare namespace monaco.editor { folding: IEditorOption; foldingStrategy: IEditorOption; foldingHighlight: IEditorOption; + unfoldOnClickInEmptyContent: IEditorOption; fontFamily: IEditorOption; fontInfo: IEditorOption; fontLigatures2: IEditorOption; diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index f9c125ee5c..6bd7488ea6 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -109,6 +109,8 @@ export interface IProductConfiguration { readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; + + readonly 'configurationSync.store'?: { url: string, authenticationProviderId: string }; } export interface IExeBasedExtensionTip { diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index bf96494742..20fb468900 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { isWindows } from 'vs/base/common/platform'; -import { coalesce } from 'vs/base/common/arrays'; import { disposableTimeout } from 'vs/base/common/async'; function uriFromRawUrl(url: string): URI | null { @@ -23,6 +22,16 @@ function uriFromRawUrl(url: string): URI | null { } } +/** + * A listener for URLs that are opened from the OS and handled by VSCode. + * Depending on the platform, this works differently: + * - Windows: we use `app.setAsDefaultProtocolClient()` to register VSCode with the OS + * and additionally add the `open-url` command line argument to identify. + * - macOS: we rely on `app.on('open-url')` to be called by the OS + * - Linux: we have a special shortcut installed (`resources/linux/code-url-handler.desktop`) + * that calls VSCode with the `open-url` command line argument + * (https://github.com/microsoft/vscode/pull/56727) + */ export class ElectronURLListener { private uris: URI[] = []; @@ -31,36 +40,34 @@ export class ElectronURLListener { private disposables = new DisposableStore(); constructor( - initial: string | string[], - @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsMainService: IWindowsMainService, - @IEnvironmentService environmentService: IEnvironmentService + initialUrisToHandle: URI[], + private readonly urlService: IURLService, + windowsMainService: IWindowsMainService, + environmentService: IEnvironmentService ) { - const globalBuffer = ((global).getOpenUrls() || []) as string[]; - const rawBuffer = [ - ...(typeof initial === 'string' ? [initial] : initial), - ...globalBuffer - ]; - this.uris = coalesce(rawBuffer.map(uriFromRawUrl)); + // the initial set of URIs we need to handle once the window is ready + this.uris = initialUrisToHandle; + // Windows: install as protocol handler if (isWindows) { const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`]; windowsParameters.push('--open-url', '--'); app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters); } + // macOS: listen to `open-url` events from here on to handle const onOpenElectronUrl = Event.map( Event.fromNodeEventEmitter(app, 'open-url', (event: ElectronEvent, url: string) => ({ event, url })), ({ event, url }) => { - // always prevent default and return the url as string - event.preventDefault(); + event.preventDefault(); // always prevent default and return the url as string return url; }); const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); + // Send initial links to the window once it has loaded const isWindowReady = windowsMainService.getWindows() .filter(w => w.isReady) .length > 0; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 49a65c8531..a6b3facf88 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -20,6 +20,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { isEqual, joinPath } from 'vs/base/common/resources'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/productService'; export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; @@ -87,6 +88,7 @@ export function registerConfiguration(): IDisposable { description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), default: true, scope: ConfigurationScope.APPLICATION, + tags: ['sync'] }, 'sync.ignoredExtensions': { 'type': 'array', @@ -95,7 +97,8 @@ export function registerConfiguration(): IDisposable { 'default': [], 'scope': ConfigurationScope.APPLICATION, uniqueItems: true, - disallowSyncIgnore: true + disallowSyncIgnore: true, + tags: ['sync'] }, 'sync.ignoredSettings': { 'type': 'array', @@ -105,7 +108,8 @@ export function registerConfiguration(): IDisposable { $ref: ignoredSettingsSchemaId, additionalProperties: true, uniqueItems: true, - disallowSyncIgnore: true + disallowSyncIgnore: true, + tags: ['sync'] } } }); @@ -140,8 +144,8 @@ export interface IUserDataSyncStore { authenticationProviderId: string; } -export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { - const value = configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY); +export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined { + const value = productService[CONFIGURATION_SYNC_STORE_KEY] || configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY); if (value && value.url && value.authenticationProviderId) { return { url: joinPath(URI.parse(value.url), 'v1'), diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 959992a9a7..2aa0634556 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -11,6 +11,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { IProductService } from 'vs/platform/product/common/productService'; export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { @@ -19,13 +20,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn readonly userDataSyncStore: IUserDataSyncStore | undefined; constructor( + @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { super(); - this.userDataSyncStore = getUserDataSyncStore(configurationService); + this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 04dc0c85a4..9c60dd34ae 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -34,6 +34,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter } from 'vs/base/common/event'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export class UserDataSyncClient extends Disposable { @@ -59,6 +61,8 @@ export class UserDataSyncClient extends Disposable { const logService = new NullLogService(); this.instantiationService.stub(ILogService, logService); + this.instantiationService.stub(IProductService, { _serviceBrand: undefined, ...product }); + const fileService = this._register(new FileService(logService)); fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); this.instantiationService.stub(IFileService, fileService); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3972f967d1..b612ec583a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1558,12 +1558,9 @@ declare module 'vscode' { label: string; /** - * Optional id for the timeline item. - */ - /** - * Optional id for the timeline item that has to be unique across your timeline source. + * Optional id for the timeline item. It must be unique across all the timeline items provided by this source. * - * If not provided, an id is generated using the timeline item's label. + * If not provided, an id is generated using the timeline item's timestamp. */ id?: string; @@ -1620,40 +1617,50 @@ declare module 'vscode' { * If the [uri](#Uri) is `undefined` that signals that the timeline source for all resources changed. */ uri?: Uri; - } - - export interface TimelineCursor { - /** - * A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable. - */ - cursor?: any; /** - * A flag to specify whether the timeline items requested are before or after (default) the provided cursor. + * A flag which indicates whether the entire timeline should be reset. */ - before?: boolean; - - /** - * The maximum number of timeline items that should be returned. - */ - limit?: number; + reset?: boolean; } export interface Timeline { - /** - * A provider-defined cursor specifing the range of timeline items returned. Must be serializable. - */ - cursor?: any; + readonly paging?: { + /** + * A set of provider-defined cursors specifing the range of timeline items returned. + */ + readonly cursors: { + readonly before: string; + readonly after?: string + }; - /** - * A flag which indicates whether there are any more items that weren't returned. - */ - more?: boolean; + /** + * A flag which indicates whether there are more items that weren't returned. + */ + readonly more?: boolean; + } /** * An array of [timeline items](#TimelineItem). */ - items: TimelineItem[]; + readonly items: readonly TimelineItem[]; + } + + export interface TimelineOptions { + /** + * A provider-defined cursor specifing the range of timeline items that should be returned. + */ + cursor?: string; + + /** + * A flag to specify whether the timeline items being requested should be before or after (default) the provided cursor. + */ + before?: boolean; + + /** + * The maximum number or the ending cursor of timeline items that should be returned. + */ + limit?: number | string; } export interface TimelineProvider { @@ -1666,23 +1673,23 @@ declare module 'vscode' { /** * An identifier of the source of the timeline items. This can be used to filter sources. */ - id: string; + readonly id: string; /** * A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources. */ - label: string; + readonly label: string; /** * Provide [timeline items](#TimelineItem) for a [Uri](#Uri). * * @param uri The [uri](#Uri) of the file to provide the timeline for. + * @param options A set of options to determine how results should be returned. * @param token A cancellation token. - * @param cursor TBD * @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result * can be signaled by returning `undefined`, `null`, or an empty array. */ - provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult; + provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult; } export namespace workspace { diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index 3bd7e23fb9..4a5fc32161 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; @extHostNamedCustomer(MainContext.MainThreadTimeline) export class MainThreadTimeline implements MainThreadTimelineShape { @@ -39,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { - return proxy.$getTimeline(provider.id, uri, cursor, token, options); + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { + return proxy.$getTimeline(provider.id, uri, options, token, internalOptions); }, dispose() { emitters.delete(provider.id); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 7f4fc54d88..5645877b12 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; -import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; @@ -1472,7 +1472,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; + $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index 6975b6b759..b7b8b44f22 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -16,7 +16,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise; + $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); @@ -50,9 +50,9 @@ export class ExtHostTimeline implements IExtHostTimeline { }); } - async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise { + async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise { const provider = this._providers.get(id); - return provider?.provideTimeline(URI.revive(uri), cursor, token, options); + return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions); } registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { @@ -70,15 +70,15 @@ export class ExtHostTimeline implements IExtHostTimeline { ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { + async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { timelineDisposables.clear(); // For now, only allow the caching of a single Uri - if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) { + if (internalOptions?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) { itemsBySourceByUriMap.clear(); } - const result = await provider.provideTimeline(uri, cursor, token); + const result = await provider.provideTimeline(uri, options, token); // Intentional == we don't know how a provider will respond // eslint-disable-next-line eqeqeq if (result == null) { @@ -86,7 +86,7 @@ export class ExtHostTimeline implements IExtHostTimeline { } // TODO: Determine if we should cache dependent on who calls us (internal vs external) - const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false); + const convertItem = convertTimelineItem(uri, internalOptions?.cacheResults ?? false); return { ...result, source: provider.id, @@ -143,6 +143,7 @@ export class ExtHostTimeline implements IExtHostTimeline { return { ...props, + id: props.id ?? undefined, handle: handle, source: source, command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 69eab38329..1e19cbf9db 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -389,7 +389,7 @@ class WebviewDocumentStore { } private key(viewType: string, resource: vscode.Uri): string { - return `${viewType}@@@${resource.toString}`; + return `${viewType}@@@${resource}`; } } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index dae76d9af4..eb3f7b3474 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -21,10 +21,13 @@ import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; const registry = Registry.as(WorkbenchExtensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); @@ -518,6 +521,92 @@ export class ResetViewLocationsAction extends Action { registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetViewLocationsAction, ResetViewLocationsAction.ID, ResetViewLocationsAction.LABEL), 'View: Reset View Locations', viewCategory); +// --- Move View with Command +export class MoveFocusedViewAction extends Action { + static readonly ID = 'workbench.action.moveFocusedView'; + static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View"); + + constructor( + id: string, + label: string, + @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, + @IViewsService private viewsService: IViewsService, + @IQuickInputService private quickInputService: IQuickInputService, + @IContextKeyService private contextKeyService: IContextKeyService, + @INotificationService private notificationService: INotificationService, + @IViewletService private viewletService: IViewletService + ) { + super(id, label); + } + + run(): Promise { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const focusedView = FocusedViewContext.getValue(this.contextKeyService); + + if (focusedView === undefined || focusedView.trim() === '') { + this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); + return Promise.resolve(); + } + + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedView); + if (!viewDescriptor || !viewDescriptor.canMoveView) { + this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable {0}.", focusedView)); + return Promise.resolve(); + } + + const quickPick = this.quickInputService.createQuickPick(); + quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a destination area for the view..."); + quickPick.autoFocusOnList = true; + + quickPick.items = [ + { + id: 'sidebar', + label: nls.localize('sidebar', "Sidebar") + }, + { + id: 'panel', + label: nls.localize('panel', "Panel") + } + ]; + + quickPick.onDidAccept(() => { + const destination = quickPick.selectedItems[0]; + + if (destination.id === 'panel') { + quickPick.hide(); + this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel); + this.viewsService.openView(focusedView, true); + + return; + } else if (destination.id === 'sidebar') { + quickPick.placeholder = nls.localize('moveFocusedView.selectDestinationContainer', "Select a destination view group..."); + quickPick.items = this.viewletService.getViewlets().map(viewlet => { + return { + id: viewlet.id, + label: viewlet.name + }; + }); + + return; + } else if (destination.id) { + quickPick.hide(); + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!); + this.viewsService.openView(focusedView, true); + return; + } + + quickPick.hide(); + }); + + quickPick.show(); + + return Promise.resolve(); + } +} + +registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory); + // --- Resize View diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 0e227d2bef..2967350ddb 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -4,19 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { posix } from 'vs/base/common/path'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations'; import { Schemas } from 'vs/base/common/network'; -import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files'; +import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { ITextModel } from 'vs/editor/common/model'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; @@ -89,7 +87,6 @@ export class ResourceLabels extends Disposable { @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, - @IFileService private readonly fileService: IFileService, @ILabelService private readonly labelService: ILabelService, @ITextFileService private readonly textFileService: ITextFileService ) { @@ -114,10 +111,6 @@ export class ResourceLabels extends Disposable { return; // we need the resource to compare } - if (this.fileService.canHandleResource(e.model.uri) && e.oldModeId === PLAINTEXT_MODE_ID) { - return; // ignore transitions in files from no mode to specific mode because this happens each time a model is created - } - this._widgets.forEach(widget => widget.notifyModelModeChanged(e.model)); })); @@ -218,11 +211,10 @@ export class ResourceLabel extends ResourceLabels { @IModelService modelService: IModelService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, - @IFileService fileService: IFileService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService); this._label = this._register(this.create(container, options)); } @@ -372,8 +364,8 @@ class ResourceLabelWidget extends IconLabel { let untitledDescription = untitledModel.resource.path; if (label.name !== untitledDescription) { label.description = untitledDescription; - } else if (label.description === posix.sep) { - label.description = undefined; // unset showing just "/" for untitled without associated resource + } else { + label.description = undefined; } } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 76a35ebfd7..4ae868f5bb 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -547,6 +547,13 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.updateFromDragging(container, true); } } + + if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { + const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype); + if (Array.isArray(data) && data[0].id !== this.activity.id) { + this.updateFromDragging(container, true); + } + } }, onDragOver: e => { @@ -575,7 +582,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem { }, onDragLeave: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) || + this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) { this.updateFromDragging(container, false); } }, diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index 232937b1a3..dd0cf76e5e 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -44,7 +44,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR KeybindingsRegistry.registerKeybindingRule({ id: descriptor.id, weight: weight, - when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null), + when: + descriptor.keybindingContext && when + ? ContextKeyExpr.and(descriptor.keybindingContext, when) + : descriptor.keybindingContext || when || null, primary: keybindings ? keybindings.primary : 0, secondary: keybindings?.secondary, win: keybindings?.win, diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 1d2205deaf..022760f889 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -154,6 +154,7 @@ .extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon { font-size: 12px; + color: currentColor; } .extensions-viewlet.narrow > .extensions .extension > .icon-container, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 66467ef2fe..2426a5f4cc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor { private static CONFIG_SCHEMA_UPDATE_DELAYER = 500; private static readonly SUGGESTIONS: string[] = [ - `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}` + `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}` ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 6520062069..a71c5acd64 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -556,8 +556,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { order: 1 }); -registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Next Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Previous Search Result', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index f1eb520fef..c50131924d 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -488,12 +488,19 @@ export class FocusNextSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, ) { super(id, label); } - run(): Promise { + async run(): Promise { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + return (this.editorService.activeControl as SearchEditor).focusNextResult(); + } + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectNextMatch(); @@ -507,12 +514,19 @@ export class FocusPreviousSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result"); constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, ) { super(id, label); } - run(): Promise { + async run(): Promise { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + // cast as we cannot import SearchEditor as a value b/c cyclic dependency. + return (this.editorService.activeControl as SearchEditor).focusPreviousResult(); + } + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectPreviousMatch(); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 88505702d3..31e3cde25a 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -181,25 +181,26 @@ export class SearchView extends ViewPane { this.container = dom.$('.search-view'); + // globals + this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService); + this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService); + this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService); + this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService); + this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); + + // scoped this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); - const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService); - viewletFocused.set(true); - - this.instantiationService = this.instantiationService.createChild( - new ServiceCollection([IContextKeyService, this.contextKeyService])); - - this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); + Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true); this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService); this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService); - this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(contextKeyService); - this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService); - this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); - this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); - this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + + this.instantiationService = this.instantiationService.createChild( + new ServiceCollection([IContextKeyService, this.contextKeyService])); this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('search.sortOrder')) { diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index 98d660959e..430f4403f5 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -13,6 +13,7 @@ export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; +export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches'; export const InSearchEditor = new RawContextKey('inSearchEditor', false); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index dd288caea4..3ed9b1c149 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -25,7 +25,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -146,6 +146,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + handler: selectAllSearchEditorMatchesCommand +}); + CommandsRegistry.registerCommand( SearchEditorConstants.RerunSearchEditorSearchCommandId, (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index f95363d5e1..4f05c901eb 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -47,6 +47,8 @@ import { assertIsDefined } from 'vs/base/common/types'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Position } from 'vs/editor/common/core/position'; +import { Selection } from 'vs/editor/common/core/selection'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -299,6 +301,42 @@ export class SearchEditor extends BaseTextEditor { return this.configurationService.getValue('search'); } + private iterateThroughMatches(reverse: boolean) { + const model = this.searchResultEditor.getModel(); + if (!model) { return; } + + const lastLine = model.getLineCount() ?? 1; + const lastColumn = model.getLineLength(lastLine); + + const fallbackStart = reverse ? new Position(lastLine, lastColumn) : new Position(1, 1); + + const currentPosition = this.searchResultEditor.getSelection()?.getStartPosition() ?? fallbackStart; + + const matchRanges = this.getInput()?.getMatchRanges(); + if (!matchRanges) { return; } + + const matchRange = (reverse ? findPrevRange : findNextRange)(matchRanges, currentPosition); + + this.searchResultEditor.setSelection(matchRange); + this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber); + this.searchResultEditor.focus(); + } + + focusNextResult() { + this.iterateThroughMatches(false); + } + + focusPreviousResult() { + this.iterateThroughMatches(true); + } + + focusAllResults() { + this.searchResultEditor + .setSelections((this.getInput()?.getMatchRanges() ?? []).map( + range => new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn))); + this.searchResultEditor.focus(); + } + async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) { const options = { resetCursor: true, delay: 0, ..._options }; @@ -307,7 +345,7 @@ export class SearchEditor extends BaseTextEditor { await this.doRunSearch(); this.toggleRunAgainMessage(false); if (options.resetCursor) { - this.searchResultEditor.setSelection(new Range(1, 1, 1, 1)); + this.searchResultEditor.setPosition(new Position(1, 1)); this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 }); } }, options.delay); @@ -523,3 +561,24 @@ registerThemingParticipant((theme, collector) => { }); export const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border.")); + +function findNextRange(matchRanges: Range[], currentPosition: Position) { + for (const matchRange of matchRanges) { + if (Position.isBefore(currentPosition, matchRange.getStartPosition())) { + return matchRange; + } + } + return matchRanges[0]; +} + +function findPrevRange(matchRanges: Range[], currentPosition: Position) { + for (let i = matchRanges.length - 1; i >= 0; i--) { + const matchRange = matchRanges[i]; + if (Position.isBefore(matchRange.getStartPosition(), currentPosition)) { + { + return matchRange; + } + } + } + return matchRanges[matchRanges.length - 1]; +} diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 3eacf6e293..421eb7397a 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -54,6 +54,14 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor } }; +export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).focusAllResults(); + } +}; + export class OpenSearchEditorAction extends Action { diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index 591d2f6895..a01f458678 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -12,6 +12,7 @@ opacity: 0.5; position: absolute; pointer-events: none; + z-index: 1; } .timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label { diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index 4f53109ecd..b7c8ee9110 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -14,6 +14,8 @@ import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineSe import { TimelinePane } from './timelinePane'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/product/common/product'; export class TimelinePaneDescriptor implements IViewDescriptor { @@ -42,13 +44,102 @@ configurationRegistry.registerConfiguration({ 'timeline.showView': { type: 'boolean', description: localize('timeline.showView', "Experimental: When enabled, shows a Timeline view in the Explorer sidebar."), - default: false //product.quality !== 'stable' + default: product.quality !== 'stable' + }, + 'timeline.excludeSources': { + type: 'array', + description: localize('timeline.excludeSources', "Experimental: An array of Timeline sources that should be excluded from the Timeline view"), + default: null }, } }); -if (product.quality !== 'stable') { - Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); +Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); + +namespace TimelineViewRefreshAction { + + export const ID = 'timeline.refresh'; + export const LABEL = localize('timeline.refreshView', "Refresh"); + + export function handler(): ICommandHandler { + return (accessor, arg) => { + const service = accessor.get(ITimelineService); + return service.reset(); + }; + } } +CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); + +// namespace TimelineViewRefreshHardAction { + +// export const ID = 'timeline.refreshHard'; +// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)"); + +// export function handler(fetch?: 'all' | 'more'): ICommandHandler { +// return (accessor, arg) => { +// const service = accessor.get(ITimelineService); +// return service.refresh(fetch); +// }; +// } +// } + +// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); + +// namespace TimelineViewLoadMoreAction { + +// export const ID = 'timeline.loadMore'; +// export const LABEL = localize('timeline.loadMoreInView', "Load More"); + +// export function handler(): ICommandHandler { +// return (accessor, arg) => { +// const service = accessor.get(ITimelineService); +// return service.refresh('more'); +// }; +// } +// } + +// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler()); + +// namespace TimelineViewLoadAllAction { + +// export const ID = 'timeline.loadAll'; +// export const LABEL = localize('timeline.loadAllInView', "Load All"); + +// export function handler(): ICommandHandler { +// return (accessor, arg) => { +// const service = accessor.get(ITimelineService); +// return service.refresh('all'); +// }; +// } +// } + +// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler()); + +MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + group: 'navigation', + order: 1, + command: { + id: TimelineViewRefreshAction.ID, + title: TimelineViewRefreshAction.LABEL, + icon: { id: 'codicon/refresh' } + } +})); + +// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ +// group: 'navigation', +// order: 2, +// command: { +// id: TimelineViewLoadMoreAction.ID, +// title: TimelineViewLoadMoreAction.LABEL, +// icon: { id: 'codicon/unfold' } +// }, +// alt: { +// id: TimelineViewLoadAllAction.ID, +// title: TimelineViewLoadAllAction.LABEL, +// icon: { id: 'codicon/unfold' } + +// } +// })); + registerSingleton(ITimelineService, TimelineService, true); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 95a7596b74..73cddec0c0 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -8,6 +8,7 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { Iterator } from 'vs/base/common/iterator'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -18,9 +19,9 @@ import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/bro import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -28,7 +29,6 @@ import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeS import { IViewDescriptorService } from 'vs/workbench/common/views'; import { basename } from 'vs/base/common/path'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { debounce } from 'vs/base/common/decorators'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -40,13 +40,53 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // TODO[ECA]: Localize all the strings -type TreeElement = TimelineItem; +const InitialPageSize = 20; +const SubsequentPageSize = 40; + +interface CommandItem { + handle: 'vscode-command:loadMore'; + timestamp: number; + label: string; + themeIcon?: { id: string }; + description?: string; + detail?: string; + contextValue?: string; + + // Make things easier for duck typing + id: undefined; + icon: undefined; + iconDark: undefined; + source: undefined; +} + +type TreeElement = TimelineItem | CommandItem; + +// function isCommandItem(item: TreeElement | undefined): item is CommandItem { +// return item?.handle.startsWith('vscode-command:') ?? false; +// } + +function isLoadMoreCommandItem(item: TreeElement | undefined): item is CommandItem & { + handle: 'vscode-command:loadMore'; +} { + return item?.handle === 'vscode-command:loadMore'; +} + +function isTimelineItem(item: TreeElement | undefined): item is TimelineItem { + return !item?.handle.startsWith('vscode-command:') ?? false; +} + interface TimelineActionContext { uri: URI | undefined; item: TreeElement; } +interface TimelineCursors { + startCursors?: { before: any; after?: any }; + endCursors?: { before: any; after?: any }; + more: boolean; +} + export class TimelinePane extends ViewPane { static readonly ID = 'timeline'; static readonly TITLE = localize('timeline', 'Timeline'); @@ -59,8 +99,9 @@ export class TimelinePane extends ViewPane { private _menus: TimelineMenus; private _visibilityDisposables: DisposableStore | undefined; - // private _excludedSources: Set | undefined; - private _items: TimelineItem[] = []; + private _excludedSources: Set; + private _cursorsByProvider: Map = new Map(); + private _items: { element: TreeElement }[] = []; private _loadingMessageTimer: any | undefined; private _pendingRequests = new Map(); private _uri: URI | undefined; @@ -87,6 +128,18 @@ export class TimelinePane extends ViewPane { const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); scopedContextKeyService.createKey('view', TimelinePane.ID); + + this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources')); + configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this); + } + + private onConfigurationChanged(e: IConfigurationChangeEvent) { + if (!e.affectsConfiguration('timeline.excludeSources')) { + return; + } + + this._excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources')); + this.loadTimeline(true); } private onActiveEditorChanged() { @@ -105,7 +158,7 @@ export class TimelinePane extends ViewPane { this._uri = uri; this._treeRenderer?.setUri(uri); - this.loadTimeline(); + this.loadTimeline(true); } private onProvidersChanged(e: TimelineProvidersChangeEvent) { @@ -116,16 +169,20 @@ export class TimelinePane extends ViewPane { } if (e.added) { - this.loadTimeline(e.added); + this.loadTimeline(true, e.added); } } private onTimelineChanged(e: TimelineChangeEvent) { - if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) { - this.loadTimeline([e.id]); + if (e?.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) { + this.loadTimeline(e.reset ?? false, e?.id === undefined ? undefined : [e.id], { before: !e.reset }); } } + private onReset() { + this.loadTimeline(true); + } + private _message: string | undefined; get message(): string | undefined { return this._message; @@ -160,22 +217,27 @@ export class TimelinePane extends ViewPane { DOM.clearNode(this._messageElement); } - private async loadTimeline(sources?: string[]) { + private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) { + const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize; + // If we have no source, we are reseting all sources, so cancel everything in flight and reset caches if (sources === undefined) { - this._items.length = 0; + if (reset) { + this._items.length = 0; + this._cursorsByProvider.clear(); - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; + if (this._loadingMessageTimer) { + clearTimeout(this._loadingMessageTimer); + this._loadingMessageTimer = undefined; + } + + for (const { tokenSource } of this._pendingRequests.values()) { + tokenSource.dispose(true); + } + + this._pendingRequests.clear(); } - for (const { tokenSource } of this._pendingRequests.values()) { - tokenSource.dispose(true); - } - - this._pendingRequests.clear(); - // TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way? if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) { this.message = 'The active editor cannot provide timeline information.'; @@ -184,7 +246,7 @@ export class TimelinePane extends ViewPane { return; } - if (this._uri !== undefined) { + if (reset && this._uri !== undefined) { this._loadingMessageTimer = setTimeout((uri: URI) => { if (uri !== this._uri) { return; @@ -200,50 +262,169 @@ export class TimelinePane extends ViewPane { return; } - for (const source of sources ?? this.timelineService.getSources()) { + const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s)); + if (filteredSources.length === 0) { + if (reset) { + this.refresh(); + } + + return; + } + + let lastIndex = this._items.length - 1; + let lastItem = this._items[lastIndex]?.element; + if (isLoadMoreCommandItem(lastItem)) { + lastItem.themeIcon = { id: 'sync~spin' }; + // this._items.splice(lastIndex, 1); + lastIndex--; + + if (!reset && !options.before) { + lastItem = this._items[lastIndex]?.element; + const selection = [lastItem]; + this._tree.setSelection(selection); + this._tree.setFocus(selection); + } + } + + for (const source of filteredSources) { let request = this._pendingRequests.get(source); - request?.tokenSource.dispose(true); - request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!; + const cursors = this._cursorsByProvider.get(source); + if (!reset) { + // TODO: Handle pending request - this._pendingRequests.set(source, request); - request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); + if (cursors?.more === false) { + continue; + } + + const reusingToken = request?.tokenSource !== undefined; + request = this.timelineService.getTimeline( + source, this._uri, + { + cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after, + ...options, + limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize + }, + request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true } + )!; + + this._pendingRequests.set(source, request); + if (!reusingToken) { + request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); + } + } else { + request?.tokenSource.dispose(true); + + request = this.timelineService.getTimeline( + source, this._uri, + { + ...options, + limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize + }, + new CancellationTokenSource(), { cacheResults: true } + )!; + + this._pendingRequests.set(source, request); + request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); + } this.handleRequest(request); } } private async handleRequest(request: TimelineRequest) { - let items; + let timeline: Timeline | undefined; try { - items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? [])); + timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result); + } + finally { + this._pendingRequests.delete(request.source); } - catch { } - this._pendingRequests.delete(request.source); - if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) { + if ( + timeline === undefined || + request.tokenSource.token.isCancellationRequested || + request.uri !== this._uri + ) { return; } - this.replaceItems(request.source, items); - } + let items: TreeElement[]; - private replaceItems(source: string, items?: TimelineItem[]) { - const hasItems = this._items.length !== 0; + const source = request.source; - if (items?.length) { - this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items); - this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + if (timeline !== undefined) { + if (timeline.paging !== undefined) { + let cursors = this._cursorsByProvider.get(timeline.source ?? source); + if (cursors === undefined) { + cursors = { startCursors: timeline.paging.cursors, more: timeline.paging.more ?? false }; + this._cursorsByProvider.set(timeline.source, cursors); + } else { + if (request.options.before) { + if (cursors.endCursors === undefined) { + cursors.endCursors = cursors.startCursors; + } + cursors.startCursors = timeline.paging.cursors; + } + else { + if (cursors.startCursors === undefined) { + cursors.startCursors = timeline.paging.cursors; + } + cursors.endCursors = timeline.paging.cursors; + } + cursors.more = timeline.paging.more ?? true; + } + } + } else { + this._cursorsByProvider.delete(source); } - else if (this._items.length && this._items.some(i => i.source === source)) { - this._items = this._items.filter(i => i.source !== source); + items = (timeline.items as TreeElement[]) ?? []; + + const alreadyHadItems = this._items.length !== 0; + + let changed; + if (request.options.cursor) { + changed = this.mergeItems(request.source, items, request.options); + } else { + changed = this.replaceItems(request.source, items); } - else { + + if (!changed) { + // If there are no items at all and no pending requests, make sure to refresh (to show the no timeline info message) + if (this._items.length === 0 && this._pendingRequests.size === 0) { + this.refresh(); + } + return; } + if (this._pendingRequests.size === 0 && this._items.length !== 0) { + const lastIndex = this._items.length - 1; + const lastItem = this._items[lastIndex]?.element; + + if (timeline.paging?.more || Iterator.some(this._cursorsByProvider.values(), cursors => cursors.more)) { + if (isLoadMoreCommandItem(lastItem)) { + lastItem.themeIcon = undefined; + } + else { + this._items.push({ + element: { + handle: 'vscode-command:loadMore', + label: 'Load more', + timestamp: 0 + } as CommandItem + }); + } + } + else { + if (isLoadMoreCommandItem(lastItem)) { + this._items.splice(lastIndex, 1); + } + } + } + // If we have items already and there are other pending requests, debounce for a bit to wait for other requests - if (hasItems && this._pendingRequests.size !== 0) { + if (alreadyHadItems && this._pendingRequests.size !== 0) { this.refreshDebounced(); } else { @@ -251,6 +432,79 @@ export class TimelinePane extends ViewPane { } } + private mergeItems(source: string, items: TreeElement[] | undefined, options: TimelineOptions): boolean { + if (items?.length === undefined || items.length === 0) { + return false; + } + + if (options.before) { + const ids = new Set(); + const timestamps = new Set(); + + for (const item of items) { + if (item.id === undefined) { + timestamps.add(item.timestamp); + } + else { + ids.add(item.id); + } + } + + // Remove any duplicate items + // I don't think we need to check all the items, just the most recent page + let i = Math.min(SubsequentPageSize, this._items.length); + let item; + while (i--) { + item = this._items[i].element; + if ( + (item.id === undefined && ids.has(item.id)) || + (item.timestamp === undefined && timestamps.has(item.timestamp)) + ) { + this._items.splice(i, 1); + } + } + + this._items.splice(0, 0, ...items.map(item => ({ element: item }))); + } else { + this._items.push(...items.map(item => ({ element: item }))); + } + + this.sortItems(); + return true; + } + + private replaceItems(source: string, items?: TreeElement[]): boolean { + if (items?.length) { + this._items.splice( + 0, this._items.length, + ...this._items.filter(item => item.element.source !== source), + ...items.map(item => ({ element: item })) + ); + this.sortItems(); + + return true; + } + + if (this._items.length && this._items.some(item => item.element.source === source)) { + this._items = this._items.filter(item => item.element.source !== source); + + return true; + } + + return false; + } + + private sortItems() { + this._items.sort( + (a, b) => + (b.element.timestamp - a.element.timestamp) || + (a.element.source === undefined + ? b.element.source === undefined ? 0 : 1 + : b.element.source === undefined ? -1 : b.element.source.localeCompare(a.element.source, undefined, { numeric: true, sensitivity: 'base' })) + ); + + } + private refresh() { if (this._loadingMessageTimer) { clearTimeout(this._loadingMessageTimer); @@ -263,7 +517,7 @@ export class TimelinePane extends ViewPane { this.message = undefined; } - this._tree.setChildren(null, this._items.map(item => ({ element: item }))); + this._tree.setChildren(null, this._items); } @debounce(500) @@ -282,6 +536,7 @@ export class TimelinePane extends ViewPane { this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables); this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables); + this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables); this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables); this.onActiveEditorChanged(); @@ -329,9 +584,24 @@ export class TimelinePane extends ViewPane { } const selection = this._tree.getSelection(); - const command = selection.length === 1 ? selection[0]?.command : undefined; - if (command) { - this.commandService.executeCommand(command.id, ...(command.arguments || [])); + const item = selection.length === 1 ? selection[0] : undefined; + // eslint-disable-next-line eqeqeq + if (item == null) { + return; + } + + if (isTimelineItem(item)) { + if (item.command) { + this.commandService.executeCommand(item.command.id, ...(item.command.arguments || [])); + } + } + else if (isLoadMoreCommandItem(item)) { + // TODO: Change this, but right now this is the pending signal + if (item.themeIcon !== undefined) { + return; + } + + this.loadTimeline(false); } }) ); @@ -417,6 +687,11 @@ export class TimelineIdentityProvider implements IIdentityProvider class TimelineActionRunner extends ActionRunner { runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { + if (!isTimelineItem(item)) { + // TODO + return action.run(); + } + return action.run(...[ { $mid: 11, @@ -499,7 +774,7 @@ class TimelineTreeRenderer implements ITreeRenderer; - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; } export interface TimelineProviderDescriptor { @@ -68,6 +75,7 @@ export interface TimelineProvidersChangeEvent { export interface TimelineRequest { readonly result: Promise; + readonly options: TimelineOptions; readonly source: string; readonly tokenSource: CancellationTokenSource; readonly uri: URI; @@ -78,13 +86,17 @@ export interface ITimelineService { onDidChangeProviders: Event; onDidChangeTimeline: Event; + onDidReset: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; getSources(): string[]; - getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }): TimelineRequest | undefined; + + // refresh(fetch?: 'all' | 'more'): void; + reset(): void; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index 0b3fd16f96..335a24f2a5 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; // import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -20,6 +20,9 @@ export class TimelineService implements ITimelineService { private readonly _onDidChangeTimeline = new Emitter(); readonly onDidChangeTimeline: Event = this._onDidChangeTimeline.event; + private readonly _onDidReset = new Emitter(); + readonly onDidReset: Event = this._onDidReset.event; + private readonly _providers = new Map(); private readonly _providerSubscriptions = new Map(); @@ -81,7 +84,7 @@ export class TimelineService implements ITimelineService { return [...this._providers.keys()]; } - getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) { + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); const provider = this._providers.get(id); @@ -98,7 +101,7 @@ export class TimelineService implements ITimelineService { } return { - result: provider.provideTimeline(uri, cursor, tokenSource.token, options) + result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions) .then(result => { if (result === undefined) { return undefined; @@ -109,6 +112,7 @@ export class TimelineService implements ITimelineService { return result; }), + options: options, source: provider.id, tokenSource: tokenSource, uri: uri @@ -156,4 +160,12 @@ export class TimelineService implements ITimelineService { this._providerSubscriptions.delete(id); this._onDidChangeProviders.fire({ removed: [id] }); } + + // refresh(fetch?: 'all' | 'more') { + // this._onDidChangeTimeline.fire({ fetch: fetch }); + // } + + reset() { + this._onDidReset.fire(); + } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 3cd5d37a10..e588584595 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -9,14 +9,16 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IUserDataSyncEnablementService, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; +import { IProductService } from 'vs/platform/product/common/productService'; class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution { constructor( + @IProductService productService: IProductService, @IConfigurationService private readonly configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, ) { - if (getUserDataSyncStore(configurationService)) { + if (getUserDataSyncStore(productService, configurationService)) { if (!configurationService.getValue('sync.enableSettings')) { userDataSyncEnablementService.setResourceEnablement('settings', false); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 2a3454705a..38a4de0e2e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -47,6 +47,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { fromNow } from 'vs/base/common/date'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; const enum AuthStatus { Initializing = 'Initializing', @@ -135,9 +137,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, + @IStorageService private readonly storageService: IStorageService, + @IOpenerService private readonly openerService: IOpenerService, ) { super(); - this.userDataSyncStore = getUserDataSyncStore(configurationService); + this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); @@ -235,7 +239,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } this.updateBadge(); - this.registerSyncStatusAction(); } private async onDidChangeSessions(providerId: string): Promise { @@ -396,7 +399,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo severity: Severity.Info, message: localize('turned off', "Sync was turned off from another device."), actions: { - primary: [new Action('turn on sync', localize('Turn sync back on', "Turn Sync Back On"), undefined, true, () => this.turnOn())] + primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())] } }); return; @@ -484,6 +487,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async turnOn(): Promise { + if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) { + const result = await this.dialogService.show( + Severity.Info, + localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), + [ + localize('open doc', "Open Documentation"), + localize('confirm', "Continue"), + localize('cancel', "Cancel"), + ], + { + cancelId: 2 + } + ); + switch (result.choice) { + case 0: this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=827846')); return; + case 2: return; + } + } return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); @@ -495,7 +516,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.customLabel = localize('turn on', "Turn on"); } else { const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); - quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName); + quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName); quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on"); } quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); @@ -523,6 +544,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on.")); + this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { @@ -746,7 +768,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerSignInAction(): void { const that = this; - registerAction2(class StopSyncAction extends Action2 { + this._register(registerAction2(class StopSyncAction extends Action2 { constructor() { super({ id: signInCommand.id, @@ -766,7 +788,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo that.notificationService.error(e); } } - }); + })); } private registerShowSettingsConflictsAction(): void { @@ -824,17 +846,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } - private readonly _syncStatusActionDisposable = this._register(new MutableDisposable()); private registerSyncStatusAction(): void { const that = this; const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)); - this._syncStatusActionDisposable.value = registerAction2(class SyncStatusAction extends Action2 { + this._register(registerAction2(class SyncStatusAction extends Action2 { constructor() { super({ id: 'workbench.userData.actions.syncStatus', - get title() { - return getIdentityTitle(localize('sync is on', "Sync is on"), that.activeAccount); - }, + title: localize('sync is on', "Sync is on"), menu: [ { id: MenuId.GlobalActivity, @@ -890,12 +909,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.show(); }); } - }); + })); } private registerTurnOffSyncAction(): void { const that = this; - registerAction2(class StopSyncAction extends Action2 { + this._register(registerAction2(class StopSyncAction extends Action2 { constructor() { super({ id: stopSyncCommand.id, @@ -915,12 +934,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } } - }); + })); } private registerConfigureSyncAction(): void { const that = this; - registerAction2(class ShowSyncActivityAction extends Action2 { + this._register(registerAction2(class ShowSyncActivityAction extends Action2 { constructor() { super({ id: configureSyncCommand.id, @@ -932,12 +951,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } run(): any { return that.configureSyncOptions(); } - }); + })); } private registerShowActivityAction(): void { const that = this; - registerAction2(class ShowSyncActivityAction extends Action2 { + this._register(registerAction2(class ShowSyncActivityAction extends Action2 { constructor() { super({ id: showSyncActivityCommand.id, @@ -949,11 +968,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } run(): any { return that.showSyncActivity(); } - }); + })); } private registerShowSettingsAction(): void { - registerAction2(class ShowSyncSettingsAction extends Action2 { + this._register(registerAction2(class ShowSyncSettingsAction extends Action2 { constructor() { super({ id: showSyncSettingsCommand.id, @@ -965,9 +984,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } run(accessor: ServicesAccessor): any { - accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' }); + accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' }); } - }); + })); } } diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 07721ce29d..5e3e1b0c17 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -34,7 +34,6 @@ import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/d import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { ILogService } from 'vs/platform/log/common/log'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -112,8 +111,7 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ILogService private readonly logService: ILogService + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -277,7 +275,9 @@ export class HistoryService extends Disposable implements IHistoryService { } clear(): void { - this.ensureHistoryLoaded(); + + // History + this.clearRecentlyOpened(); // Navigation (next, previous) this.navigationStackIndex = -1; @@ -289,9 +289,6 @@ export class HistoryService extends Disposable implements IHistoryService { // Closed files this.recentlyClosedFiles = []; - // History - this.clearRecentlyOpened(); - // Context Keys this.updateContextKeys(); } @@ -719,8 +716,8 @@ export class HistoryService extends Disposable implements IHistoryService { private static readonly MAX_HISTORY_ITEMS = 200; private static readonly HISTORY_STORAGE_KEY = 'history.entries'; - private history: Array = []; - private loaded = false; + private history: Array | undefined = undefined; + private readonly resourceFilter = this._register(this.instantiationService.createInstance( ResourceGlobMatcher, (root?: URI) => this.getExcludes(root), @@ -741,11 +738,10 @@ export class HistoryService extends Disposable implements IHistoryService { return; } - this.ensureHistoryLoaded(); - const historyInput = this.preferResourceInput(input); // Remove any existing entry and add to the beginning + this.ensureHistoryLoaded(this.history); this.removeFromHistory(input); this.history.unshift(historyInput); @@ -772,7 +768,7 @@ export class HistoryService extends Disposable implements IHistoryService { } private removeExcludedFromHistory(): void { - this.ensureHistoryLoaded(); + this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { const include = this.include(e); @@ -787,7 +783,7 @@ export class HistoryService extends Disposable implements IHistoryService { } private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { - this.ensureHistoryLoaded(); + this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { const matches = this.matches(arg1, e); @@ -809,17 +805,59 @@ export class HistoryService extends Disposable implements IHistoryService { } getHistory(): ReadonlyArray { - this.ensureHistoryLoaded(); + this.ensureHistoryLoaded(this.history); return this.history.slice(0); } - private ensureHistoryLoaded(): void { - if (!this.loaded) { - this.loadHistory(); + private ensureHistoryLoaded(history: Array | undefined): asserts history { + if (!this.history) { + this.history = this.loadHistory(); + } + } + + private loadHistory(): Array { + let entries: ISerializedEditorHistoryEntry[] = []; + + const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); + if (entriesRaw) { + entries = coalesce(JSON.parse(entriesRaw)); } - this.loaded = true; + const registry = Registry.as(EditorExtensions.EditorInputFactories); + + return coalesce(entries.map(entry => { + try { + return this.safeLoadHistoryEntry(registry, entry); + } catch (error) { + return undefined; // https://github.com/Microsoft/vscode/issues/60960 + } + })); + } + + private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined { + const serializedEditorHistoryEntry = entry; + + // File resource: via URI.revive() + if (serializedEditorHistoryEntry.resourceJSON) { + return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) }; + } + + // Editor input: via factory + const { editorInputJSON } = serializedEditorHistoryEntry; + if (editorInputJSON?.deserialized) { + const factory = registry.getEditorInputFactory(editorInputJSON.typeId); + if (factory) { + const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); + if (input) { + this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); + } + + return withNullAsUndefined(input); + } + } + + return undefined; } private saveState(): void { @@ -850,58 +888,9 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; })); - this.logService.trace(`[editor history] saving ${entries.length} entries`); this.storageService.store(HistoryService.HISTORY_STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE); } - private loadHistory(): void { - let entries: ISerializedEditorHistoryEntry[] = []; - - const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); - if (entriesRaw) { - entries = coalesce(JSON.parse(entriesRaw)); - } - - const registry = Registry.as(EditorExtensions.EditorInputFactories); - - this.history = coalesce(entries.map(entry => { - try { - return this.safeLoadHistoryEntry(registry, entry); - } catch (error) { - this.logService.error(`[editor history] error loading one editor history entry: ${error.toString()}`); - - return undefined; // https://github.com/Microsoft/vscode/issues/60960 - } - })); - - this.logService.trace(`[editor history] loading ${this.history.length} entries`); - } - - private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined { - const serializedEditorHistoryEntry = entry; - - // File resource: via URI.revive() - if (serializedEditorHistoryEntry.resourceJSON) { - return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) }; - } - - // Editor input: via factory - const { editorInputJSON } = serializedEditorHistoryEntry; - if (editorInputJSON?.deserialized) { - const factory = registry.getEditorInputFactory(editorInputJSON.typeId); - if (factory) { - const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); - if (input) { - this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); - } - - return withNullAsUndefined(input); - } - } - - return undefined; - } - //#endregion //#region Last Active Workspace/File @@ -925,8 +914,7 @@ export class HistoryService extends Disposable implements IHistoryService { } // Multiple folders: find the last active one - const history = this.getHistory(); - for (const input of history) { + for (const input of this.getHistory()) { if (input instanceof EditorInput) { continue; } @@ -954,8 +942,7 @@ export class HistoryService extends Disposable implements IHistoryService { } getLastActiveFile(filterByScheme: string): URI | undefined { - const history = this.getHistory(); - for (const input of history) { + for (const input of this.getHistory()) { let resource: URI | undefined; if (input instanceof EditorInput) { resource = toResource(input, { filterByScheme }); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 5d4e1cf6c1..d0a3390319 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -100,7 +100,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.labelService.getUriBasenameLabel(this.resource); } - private dirty = false; + private dirty = this.hasAssociatedFilePath || !!this.initialValue; private ignoreDirtyOnModelContentChange = false; private versionId = 0; diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index ff499e7631..60b53299e5 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -11,7 +11,6 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; @@ -120,9 +119,13 @@ suite('Untitled text editors', () => { test('associated resource is dirty', async () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = await service.resolve({ associatedResource: file }); - assert.ok(untitled.hasAssociatedFilePath); + const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file })); + assert.ok(untitled.isDirty()); + + const model = await untitled.resolve(); + + assert.ok(model.hasAssociatedFilePath); assert.equal(untitled.isDirty(), true); untitled.dispose(); @@ -197,20 +200,14 @@ suite('Untitled text editors', () => { const workingCopyService = accessor.workingCopyService; const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' })); - - let onDidChangeDirty: IWorkingCopy | undefined = undefined; - const listener = workingCopyService.onDidChangeDirty(copy => { - onDidChangeDirty = copy; - }); + assert.ok(untitled.isDirty()); // dirty const model = await untitled.resolve(); assert.ok(model.isDirty()); assert.equal(workingCopyService.dirtyCount, 1); - assert.equal(onDidChangeDirty, model); untitled.dispose(); - listener.dispose(); model.dispose(); }); diff --git a/yarn.lock b/yarn.lock index a7d885ed99..225827cf1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -685,6 +685,11 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= +ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + ajv@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" @@ -1596,7 +1601,7 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^5.0.0: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -2308,6 +2313,24 @@ css-color-names@0.0.4: resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= +css-loader@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2" + integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ== + dependencies: + camelcase "^5.3.1" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.17" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.1.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.0.0" + schema-utils "^2.0.0" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -2323,6 +2346,11 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0= +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssnano@^3.0.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" @@ -3386,6 +3414,14 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" +file-loader@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e" + integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.0.0" + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -4655,6 +4691,13 @@ iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + ieee754@^1.1.11, ieee754@^1.1.4: version "1.1.12" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" @@ -5625,7 +5668,7 @@ loader-runner@^2.3.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI= -loader-utils@1.2.3: +loader-utils@1.2.3, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -7309,6 +7352,39 @@ postcss-minify-selectors@^2.0.4: postcss "^5.0.14" postcss-selector-parser "^2.0.0" +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-local-by-default@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" + integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.16" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.0" + +postcss-modules-scope@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb" + integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + postcss-normalize-charset@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" @@ -7367,6 +7443,15 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: indexes-of "^1.0.1" uniq "^1.0.1" +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-svgo@^2.1.1: version "2.1.6" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" @@ -7391,6 +7476,11 @@ postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU= +postcss-value-parser@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" + integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== + postcss-zindex@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" @@ -7410,10 +7500,10 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^7.0.5: - version "7.0.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" - integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== +postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -8294,6 +8384,14 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +schema-utils@^2.0.0, schema-utils@^2.0.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f" + integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ== + dependencies: + ajv "^6.10.2" + ajv-keywords "^3.4.1" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -8908,6 +9006,14 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== +style-loader@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82" + integrity sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.0.1" + sudo-prompt@9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" @@ -9812,10 +9918,10 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-debugprotocol@1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d" - integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA== +vscode-debugprotocol@1.39.0-pre.0: + version "1.39.0-pre.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.39.0-pre.0.tgz#67843631a3c53f2d5282f75ab5996e1408c3958c" + integrity sha512-VpoD8m0gOo2Ag5dEpNT9sAI6BBxIyCxEk2dhGIBegxnlOuiB1SVxMgo1tmsvNYcRCpf9eng27kZ6d6FGoPpIAg== vscode-minimist@^1.2.2: version "1.2.2"