mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317)
This commit is contained in:
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
21
build/monaco/esm.core.js
Normal 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'
|
||||
});
|
||||
44
build/monaco/monaco.webpack.config.js
Normal file
44
build/monaco/monaco.webpack.config.js
Normal 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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
1
extensions/github-authentication/.gitignore
vendored
Normal file
1
extensions/github-authentication/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/common/config.json
|
||||
7
extensions/github-authentication/README.md
Normal file
7
extensions/github-authentication/README.md
Normal 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.
|
||||
26
extensions/github-authentication/build/postinstall.js
Normal file
26
extensions/github-authentication/build/postinstall.js
Normal 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();
|
||||
20
extensions/github-authentication/extension.webpack.config.js
Normal file
20
extensions/github-authentication/extension.webpack.config.js
Normal 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'
|
||||
}
|
||||
});
|
||||
33
extensions/github-authentication/package.json
Normal file
33
extensions/github-authentication/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
4
extensions/github-authentication/package.nls.json
Normal file
4
extensions/github-authentication/package.nls.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"displayName": "GitHub Authentication",
|
||||
"description": "GitHub Authentication Provider"
|
||||
}
|
||||
@@ -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;
|
||||
73
extensions/github-authentication/src/common/keychain.ts
Normal file
73
extensions/github-authentication/src/common/keychain.ts
Normal 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();
|
||||
55
extensions/github-authentication/src/common/logger.ts
Normal file
55
extensions/github-authentication/src/common/logger.ts
Normal 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;
|
||||
73
extensions/github-authentication/src/common/utils.ts
Normal file
73
extensions/github-authentication/src/common/utils.ts
Normal 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
43
extensions/github-authentication/src/extension.ts
Normal file
43
extensions/github-authentication/src/extension.ts
Normal 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() { }
|
||||
113
extensions/github-authentication/src/github.ts
Normal file
113
extensions/github-authentication/src/github.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
114
extensions/github-authentication/src/githubServer.ts
Normal file
114
extensions/github-authentication/src/githubServer.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
7
extensions/github-authentication/src/typings/ref.d.ts
vendored
Normal file
7
extensions/github-authentication/src/typings/ref.d.ts
vendored
Normal 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'/>
|
||||
13
extensions/github-authentication/tsconfig.json
Normal file
13
extensions/github-authentication/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../shared.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"experimentalDecorators": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
454
extensions/github-authentication/yarn.lock
Normal file
454
extensions/github-authentication/yarn.lock
Normal 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
@@ -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",
|
||||
|
||||
@@ -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[]}
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ class Widget {
|
||||
|
||||
return {
|
||||
fitsAbove,
|
||||
aboveTop: Math.max(aboveTop, TOP_PADDING),
|
||||
aboveTop: aboveTop,
|
||||
aboveLeft,
|
||||
fitsBelow,
|
||||
belowTop,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.") }
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>');
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
165
src/vs/monaco.d.ts
vendored
@@ -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>;
|
||||
|
||||
@@ -109,6 +109,8 @@ export interface IProductConfiguration {
|
||||
|
||||
readonly msftInternalDomains?: string[];
|
||||
readonly linkProtectionTrustedDomains?: readonly string[];
|
||||
|
||||
readonly 'configurationSync.store'?: { url: string, authenticationProviderId: string };
|
||||
}
|
||||
|
||||
export interface IExeBasedExtensionTip {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
71
src/vs/vscode.proposed.d.ts
vendored
71
src/vs/vscode.proposed.d.ts
vendored
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -389,7 +389,7 @@ class WebviewDocumentStore {
|
||||
}
|
||||
|
||||
private key(viewType: string, resource: vscode.Uri): string {
|
||||
return `${viewType}@@@${resource.toString}`;
|
||||
return `${viewType}@@@${resource}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
126
yarn.lock
@@ -685,6 +685,11 @@ ajv-keywords@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
|
||||
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
|
||||
|
||||
ajv-keywords@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
|
||||
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
|
||||
|
||||
ajv@^5.1.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
|
||||
@@ -1596,7 +1601,7 @@ camelcase@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
|
||||
integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
|
||||
|
||||
camelcase@^5.0.0:
|
||||
camelcase@^5.0.0, camelcase@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
@@ -2308,6 +2313,24 @@ css-color-names@0.0.4:
|
||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||
integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
|
||||
|
||||
css-loader@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2"
|
||||
integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==
|
||||
dependencies:
|
||||
camelcase "^5.3.1"
|
||||
cssesc "^3.0.0"
|
||||
icss-utils "^4.1.1"
|
||||
loader-utils "^1.2.3"
|
||||
normalize-path "^3.0.0"
|
||||
postcss "^7.0.17"
|
||||
postcss-modules-extract-imports "^2.0.0"
|
||||
postcss-modules-local-by-default "^3.0.2"
|
||||
postcss-modules-scope "^2.1.0"
|
||||
postcss-modules-values "^3.0.0"
|
||||
postcss-value-parser "^4.0.0"
|
||||
schema-utils "^2.0.0"
|
||||
|
||||
css-select@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
|
||||
@@ -2323,6 +2346,11 @@ css-what@2.1:
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
|
||||
integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0=
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
cssnano@^3.0.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
|
||||
@@ -3386,6 +3414,14 @@ file-entry-cache@^5.0.1:
|
||||
dependencies:
|
||||
flat-cache "^2.0.1"
|
||||
|
||||
file-loader@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e"
|
||||
integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==
|
||||
dependencies:
|
||||
loader-utils "^1.2.3"
|
||||
schema-utils "^2.0.0"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
@@ -4655,6 +4691,13 @@ iconv-lite@^0.4.4:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
|
||||
integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
|
||||
dependencies:
|
||||
postcss "^7.0.14"
|
||||
|
||||
ieee754@^1.1.11, ieee754@^1.1.4:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
@@ -5625,7 +5668,7 @@ loader-runner@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
|
||||
integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
|
||||
|
||||
loader-utils@1.2.3:
|
||||
loader-utils@1.2.3, loader-utils@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
||||
@@ -7309,6 +7352,39 @@ postcss-minify-selectors@^2.0.4:
|
||||
postcss "^5.0.14"
|
||||
postcss-selector-parser "^2.0.0"
|
||||
|
||||
postcss-modules-extract-imports@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
|
||||
integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==
|
||||
dependencies:
|
||||
postcss "^7.0.5"
|
||||
|
||||
postcss-modules-local-by-default@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915"
|
||||
integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==
|
||||
dependencies:
|
||||
icss-utils "^4.1.1"
|
||||
postcss "^7.0.16"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
postcss-value-parser "^4.0.0"
|
||||
|
||||
postcss-modules-scope@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb"
|
||||
integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==
|
||||
dependencies:
|
||||
postcss "^7.0.6"
|
||||
postcss-selector-parser "^6.0.0"
|
||||
|
||||
postcss-modules-values@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
|
||||
integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
|
||||
dependencies:
|
||||
icss-utils "^4.0.0"
|
||||
postcss "^7.0.6"
|
||||
|
||||
postcss-normalize-charset@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
|
||||
@@ -7367,6 +7443,15 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
|
||||
integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-svgo@^2.1.1:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d"
|
||||
@@ -7391,6 +7476,11 @@ postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
|
||||
integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=
|
||||
|
||||
postcss-value-parser@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
|
||||
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
|
||||
|
||||
postcss-zindex@^2.0.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
|
||||
@@ -7410,10 +7500,10 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
|
||||
source-map "^0.5.6"
|
||||
supports-color "^3.2.3"
|
||||
|
||||
postcss@^7.0.5:
|
||||
version "7.0.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5"
|
||||
integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
|
||||
postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0.6:
|
||||
version "7.0.21"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17"
|
||||
integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
@@ -8294,6 +8384,14 @@ schema-utils@^0.4.4, schema-utils@^0.4.5:
|
||||
ajv "^6.1.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^2.0.0, schema-utils@^2.0.1:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f"
|
||||
integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==
|
||||
dependencies:
|
||||
ajv "^6.10.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
@@ -8908,6 +9006,14 @@ strip-json-comments@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
||||
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
|
||||
|
||||
style-loader@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82"
|
||||
integrity sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==
|
||||
dependencies:
|
||||
loader-utils "^1.2.3"
|
||||
schema-utils "^2.0.1"
|
||||
|
||||
sudo-prompt@9.1.1:
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0"
|
||||
@@ -9812,10 +9918,10 @@ vsce@1.48.0:
|
||||
yauzl "^2.3.1"
|
||||
yazl "^2.2.2"
|
||||
|
||||
vscode-debugprotocol@1.37.0:
|
||||
version "1.37.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d"
|
||||
integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA==
|
||||
vscode-debugprotocol@1.39.0-pre.0:
|
||||
version "1.39.0-pre.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.39.0-pre.0.tgz#67843631a3c53f2d5282f75ab5996e1408c3958c"
|
||||
integrity sha512-VpoD8m0gOo2Ag5dEpNT9sAI6BBxIyCxEk2dhGIBegxnlOuiB1SVxMgo1tmsvNYcRCpf9eng27kZ6d6FGoPpIAg==
|
||||
|
||||
vscode-minimist@^1.2.2:
|
||||
version "1.2.2"
|
||||
|
||||
Reference in New Issue
Block a user