Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317)

This commit is contained in:
Anthony Dresser
2020-02-24 21:15:52 -08:00
committed by GitHub
parent 628fd8d74d
commit 4a9c47d3d6
93 changed files with 3109 additions and 813 deletions

View File

@@ -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

1
.gitignore vendored
View File

@@ -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/

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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};`);
}
}
}

View File

@@ -167,10 +167,11 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati
result = result.replace(memberText, '');
} else {
const memberName = (<ts.Identifier | ts.StringLiteral>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) {

21
build/monaco/esm.core.js Normal file
View File

@@ -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'
});

View File

@@ -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
}
};

View File

@@ -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

View File

@@ -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."
]
}
]

View File

@@ -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": {

View File

@@ -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<Commit[]> {
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) {

View File

@@ -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<Timeline> {
async provideTimeline(uri: Uri, options: TimelineOptions, _token: CancellationToken): Promise<Timeline> {
// 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<GitTimelineItem>(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 });
}
}

View File

@@ -0,0 +1 @@
src/common/config.json

View File

@@ -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.

View File

@@ -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();

View File

@@ -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'
}
});

View File

@@ -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"
}
}

View File

@@ -0,0 +1,4 @@
{
"displayName": "GitHub Authentication",
"description": "GitHub Authentication Provider"
}

View File

@@ -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;

View File

@@ -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<void> {
try {
return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token);
} catch (e) {
// Ignore
Logger.error(`Setting token failed: ${e}`);
}
}
async getToken(): Promise<string | null | undefined> {
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<boolean | undefined> {
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();

View File

@@ -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;

View File

@@ -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<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
}
export function onceEvent<T>(event: Event<T>): Event<T> {
return (listener, thisArgs = null, disposables?) => {
const result = event(e => {
result.dispose();
return listener.call(thisArgs, e);
}, null, disposables);
return result;
};
}
export interface PromiseAdapter<T, U> {
(
value: T,
resolve:
(value?: U | PromiseLike<U>) => 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<T, U>(
event: Event<T>,
adapter: PromiseAdapter<T, U> = passthrough): Promise<U> {
let subscription: Disposable;
return new Promise<U>((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;
}
);
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * 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() { }

View File

@@ -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<void>();
export class GitHubAuthenticationProvider {
private _sessions: vscode.AuthenticationSession[] = [];
private _githubServer = new GitHubServer();
public async initialize(): Promise<void> {
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<vscode.AuthenticationSession[]> {
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<void> {
await keychain.setToken(JSON.stringify(this._sessions));
}
get sessions(): vscode.AuthenticationSession[] {
return this._sessions;
}
public async login(scopes: string): Promise<vscode.AuthenticationSession> {
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<vscode.AuthenticationSession> {
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<void> {
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();
}
}

View File

@@ -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<vscode.Uri> implements vscode.UriHandler {
public handleUri(uri: vscode.Uri) {
this.fire(uri);
}
}
export const uriHandler = new UriEventHandler;
const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter<vscode.Uri, string> =
(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<string> {
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);
});
});
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>

View File

@@ -0,0 +1,13 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true,
"typeRoots": [
"./node_modules/@types"
]
},
"include": [
"src/**/*"
]
}

View File

@@ -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=

File diff suppressed because one or more lines are too long

View File

@@ -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",

View File

@@ -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[]}
*/

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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();
}
}
}
}

View File

@@ -125,6 +125,19 @@ export module Iterator {
};
}
export function some<T>(iterator: Iterator<T> | NativeIterator<T>, 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<T>(iterator: Iterator<T>, fn: (t: T) => void): void {
for (let next = iterator.next(); !next.done; next = iterator.next()) {
fn(next.value);

View File

@@ -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));
}

View File

@@ -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<void> {
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'
};
}

View File

@@ -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<string | null> {
return new Promise<string | null>((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 && (<any>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;

View File

@@ -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
...((<any>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<boolean> {
async handleURL(uri: URI): Promise<boolean> {
// 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<boolean> {
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[] = (<any>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<TContext> extends ExtensionHost
super();
}
call(ctx: TContext, command: string, arg?: any): Promise<any> {
async call(ctx: TContext, command: string, arg?: any): Promise<any> {
if (command === 'openExtensionDevelopmentHostWindow') {
const env = arg[1];
const pargs = parseArgs(arg[0], OPTIONS);
@@ -775,7 +817,6 @@ class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHost
userEnv: Object.keys(env).length > 0 ? env : undefined
});
}
return Promise.resolve();
} else {
return super.call(ctx, command, arg);
}

View File

@@ -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;
}

View File

@@ -369,7 +369,7 @@ class Widget {
return {
fitsAbove,
aboveTop: Math.max(aboveTop, TOP_PADDING),
aboveTop: aboveTop,
aboveLeft,
fitsBelow,
belowTop,

View File

@@ -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 <span>
// To cover cases of empty <span>s, aboid using a range and use the <span>'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) {

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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',

View File

@@ -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.") }

View File

@@ -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));

View File

@@ -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
}
/**

View File

@@ -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);

View File

@@ -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('<span class="');
@@ -832,7 +855,8 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
sb.appendASCII(CharCode.GreaterThan);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
partDisplacement = 0;
const charCode = lineContent.charCodeAt(charIndex);
let charWidth: number;
@@ -872,7 +896,8 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
sb.appendASCII(CharCode.GreaterThan);
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
partDisplacement = 0;
const charCode = lineContent.charCodeAt(charIndex);
let producedCharacters = 1;
@@ -933,6 +958,12 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
prevPartContentCnt = partContentCnt;
}
if (partIsEmptyAndHasPseudoAfter) {
partDisplacement++;
} else {
partDisplacement = 0;
}
sb.appendASCIIString('</span>');
}

View File

@@ -19,7 +19,7 @@ import { FoldingDecorationProvider } from './foldingDecorations';
import { FoldingRegions, FoldingRegion } from './foldingRanges';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
import { IMarginData, IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel';
import { IRange } from 'vs/editor/common/core/range';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
@@ -62,6 +62,7 @@ export class FoldingController extends Disposable implements IEditorContribution
private readonly editor: ICodeEditor;
private _isEnabled: boolean;
private _useFoldingProviders: boolean;
private _unfoldOnClickInEmptyContent: boolean;
private readonly foldingDecorationProvider: FoldingDecorationProvider;
@@ -91,6 +92,7 @@ export class FoldingController extends Disposable implements IEditorContribution
const options = this.editor.getOptions();
this._isEnabled = options.get(EditorOption.folding);
this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';
this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent);
this.foldingModel = null;
this.hiddenRangeModel = null;
@@ -128,6 +130,9 @@ export class FoldingController extends Disposable implements IEditorContribution
this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';
this.onFoldingStrategyChanged();
}
if (e.hasChanged(EditorOption.unfoldOnClickInEmptyContent)) {
this._unfoldOnClickInEmptyContent = options.get(EditorOption.unfoldOnClickInEmptyContent);
}
}));
this.onModelChanged();
}
@@ -364,6 +369,15 @@ export class FoldingController extends Disposable implements IEditorContribution
iconClicked = true;
break;
case MouseTargetType.CONTENT_EMPTY: {
if (this._unfoldOnClickInEmptyContent && this.hiddenRangeModel.hasRanges()) {
const data = e.target.detail as IEmptyContentData;
if (!data.isAfterLines) {
break;
}
}
return;
}
case MouseTargetType.CONTENT_TEXT: {
if (this.hiddenRangeModel.hasRanges()) {
let model = this.editor.getModel();

View File

@@ -397,7 +397,7 @@ class MarkerNavigationAction extends EditorAction {
return editorService.openCodeEditor({
resource: newMarker.resource,
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, selection: newMarker }
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: newMarker }
}, editor).then(editor => {
if (!editor) {
return undefined;

View File

@@ -52,11 +52,11 @@ class MessageWidget {
const domNode = document.createElement('div');
domNode.className = 'descriptioncontainer';
domNode.setAttribute('aria-live', 'assertive');
domNode.setAttribute('role', 'alert');
this._messageBlock = document.createElement('div');
dom.addClass(this._messageBlock, 'message');
this._messageBlock.setAttribute('aria-live', 'assertive');
this._messageBlock.setAttribute('role', 'alert');
domNode.appendChild(this._messageBlock);
this._relatedBlock = document.createElement('div');
@@ -88,7 +88,8 @@ class MessageWidget {
dispose(this._disposables);
}
update({ source, message, relatedInformation, code }: IMarker): void {
update(marker: IMarker): void {
const { source, message, relatedInformation, code } = marker;
let sourceAndCodeLength = (source?.length || 0) + '()'.length;
if (code) {
if (typeof code === 'string') {
@@ -106,6 +107,7 @@ class MessageWidget {
}
dom.clearNode(this._messageBlock);
this._messageBlock.setAttribute('aria-label', this.getAriaLabel(marker));
this._editor.applyFontInfo(this._messageBlock);
let lastLineElement = this._messageBlock;
for (const line of lines) {
@@ -192,6 +194,32 @@ class MessageWidget {
getHeightInLines(): number {
return Math.min(17, this._lines);
}
private getAriaLabel(marker: IMarker): string {
let severityLabel = '';
switch (marker.severity) {
case MarkerSeverity.Error:
severityLabel = nls.localize('Error', "Error");
break;
case MarkerSeverity.Warning:
severityLabel = nls.localize('Warning', "Warning");
break;
case MarkerSeverity.Info:
severityLabel = nls.localize('Info', "Info");
break;
case MarkerSeverity.Hint:
severityLabel = nls.localize('Hint', "Hint");
break;
}
let ariaLabel = nls.localize('marker aria', "{0} at {1}. ", severityLabel, marker.startLineNumber + ':' + marker.startColumn);
const model = this._editor.getModel();
if (model && (marker.startLineNumber <= model.getLineCount()) && (marker.startLineNumber >= 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();
}

View File

@@ -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)
]);
});
});

View File

@@ -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 = [
'<span class="mtk1">//just\u00a0a\u00a0com</span>',
'<span class="mtk1 dec2"></span>',
'<span class="mtk1 dec1"></span>',
'<span class="mtk1">ment</span>',
].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, '<span>' + expectedOutput + '</span>');
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, '<span>' + expectedOutput + '</span>');
});
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);

165
src/vs/monaco.d.ts vendored
View File

@@ -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<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
@@ -3939,6 +3945,7 @@ declare namespace monaco.editor {
folding: IEditorOption<EditorOption.folding, boolean>;
foldingStrategy: IEditorOption<EditorOption.foldingStrategy, 'auto' | 'indentation'>;
foldingHighlight: IEditorOption<EditorOption.foldingHighlight, boolean>;
unfoldOnClickInEmptyContent: IEditorOption<EditorOption.unfoldOnClickInEmptyContent, boolean>;
fontFamily: IEditorOption<EditorOption.fontFamily, string>;
fontInfo: IEditorOption<EditorOption.fontInfo, FontInfo>;
fontLigatures2: IEditorOption<EditorOption.fontLigatures, string>;

View File

@@ -109,6 +109,8 @@ export interface IProductConfiguration {
readonly msftInternalDomains?: string[];
readonly linkProtectionTrustedDomains?: readonly string[];
readonly 'configurationSync.store'?: { url: string, authenticationProviderId: string };
}
export interface IExeBasedExtensionTip {

View File

@@ -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 = ((<any>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<URI | null, URI>(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;

View File

@@ -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'),

View File

@@ -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<IUserData> {

View File

@@ -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);

View File

@@ -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<Timeline>;
provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult<Timeline>;
}
export namespace workspace {

View File

@@ -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);

View File

@@ -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<Timeline | undefined>;
$getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
// --- proxy identifiers

View File

@@ -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<Timeline | undefined>;
$getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('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<Timeline | undefined> {
async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
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,

View File

@@ -389,7 +389,7 @@ class WebviewDocumentStore {
}
private key(viewType: string, resource: vscode.Uri): string {
return `${viewType}@@@${resource.toString}`;
return `${viewType}@@@${resource}`;
}
}

View File

@@ -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<IWorkbenchActionRegistry>(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<void> {
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(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

View File

@@ -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;
}
}

View File

@@ -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);
}
},

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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, {

View File

@@ -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<any> {
async run(): Promise<any> {
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<any> {
async run(): Promise<any> {
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();

View File

@@ -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')) {

View File

@@ -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<boolean>('inSearchEditor', false);

View File

@@ -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) => {

View File

@@ -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<ISearchConfigurationProperties>('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];
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
Registry.as<IViewsRegistry>(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);

View File

@@ -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<string> | undefined;
private _items: TimelineItem[] = [];
private _excludedSources: Set<string>;
private _cursorsByProvider: Map<string, TimelineCursors> = new Map();
private _items: { element: TreeElement }[] = [];
private _loadingMessageTimer: any | undefined;
private _pendingRequests = new Map<string, TimelineRequest>();
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<TreeElement>
class TimelineActionRunner extends ActionRunner {
runAction(action: IAction, { uri, item }: TimelineActionContext): Promise<any> {
if (!isTimelineItem(item)) {
// TODO
return action.run();
}
return action.run(...[
{
$mid: 11,
@@ -499,7 +774,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
matches: createMatches(node.filterData)
});
template.timestamp.textContent = fromNow(item.timestamp);
template.timestamp.textContent = isTimelineItem(item) ? fromNow(item.timestamp) : '';
template.actionBar.context = { uri: this._uri, item: item } as TimelineActionContext;
template.actionBar.actionRunner = new TimelineActionRunner();

View File

@@ -19,6 +19,7 @@ export interface TimelineItem {
handle: string;
source: string;
id?: string;
timestamp: number;
label: string;
icon?: URI,
@@ -31,28 +32,34 @@ export interface TimelineItem {
}
export interface TimelineChangeEvent {
id: string;
id?: string;
uri?: URI;
reset?: boolean
}
export interface TimelineCursor {
cursor?: any;
export interface TimelineOptions {
cursor?: string;
before?: boolean;
limit?: number;
limit?: number | string;
}
export interface Timeline {
source: string;
items: TimelineItem[];
cursor?: any;
more?: boolean;
paging?: {
cursors: {
before: string;
after?: string
};
more?: boolean;
}
}
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
onDidChange?: Event<TimelineChangeEvent>;
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
export interface TimelineProviderDescriptor {
@@ -68,6 +75,7 @@ export interface TimelineProvidersChangeEvent {
export interface TimelineRequest {
readonly result: Promise<Timeline | undefined>;
readonly options: TimelineOptions;
readonly source: string;
readonly tokenSource: CancellationTokenSource;
readonly uri: URI;
@@ -78,13 +86,17 @@ export interface ITimelineService {
onDidChangeProviders: Event<TimelineProvidersChangeEvent>;
onDidChangeTimeline: Event<TimelineChangeEvent>;
onDidReset: Event<void>;
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';

View File

@@ -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<TimelineChangeEvent>();
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
private readonly _onDidReset = new Emitter<void>();
readonly onDidReset: Event<void> = this._onDidReset.event;
private readonly _providers = new Map<string, TimelineProvider>();
private readonly _providerSubscriptions = new Map<string, IDisposable>();
@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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<void> {
@@ -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<void> {
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<ConfigureSyncQuickPickItem>();
@@ -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' });
}
});
}));
}
}

View File

@@ -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<IEditorInput | IResourceInput> = [];
private loaded = false;
private history: Array<IEditorInput | IResourceInput> | 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<IEditorInput | IResourceInput> {
this.ensureHistoryLoaded();
this.ensureHistoryLoaded(this.history);
return this.history.slice(0);
}
private ensureHistoryLoaded(): void {
if (!this.loaded) {
this.loadHistory();
private ensureHistoryLoaded(history: Array<IEditorInput | IResourceInput> | undefined): asserts history {
if (!this.history) {
this.history = this.loadHistory();
}
}
private loadHistory(): Array<IEditorInput | IResourceInput> {
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<IEditorInputFactoryRegistry>(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(<UriComponents>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<IEditorInputFactoryRegistry>(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(<UriComponents>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 });

View File

@@ -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;

View File

@@ -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();
});

126
yarn.lock
View File

@@ -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"