Merge branch 'master' into misolori/scm-icons

This commit is contained in:
Miguel Solorio 2020-03-09 13:37:28 -07:00 committed by GitHub
commit 75b86ddb70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
220 changed files with 9008 additions and 1903 deletions

74
.vscode/searches/es6.code-search vendored Normal file
View file

@ -0,0 +1,74 @@
# Query: @deprecated ES6
# Flags: CaseSensitive WordMatch
# ContextLines: 2
11 results - 3 files
src/vs/base/common/arrays.ts:
401
402 /**
403: * @deprecated ES6: use `Array.findIndex`
404 */
405 export function firstIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): number {
417
418 /**
419: * @deprecated ES6: use `Array.find`
420 */
421 export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T): T;
474
475 /**
476: * @deprecated ES6: use `Array.fill`
477 */
478 export function fill<T>(num: number, value: T, arr: T[] = []): T[] {
571
572 /**
573: * @deprecated ES6: use `Array.find`
574 */
575 export function find<T>(arr: ArrayLike<T>, predicate: (value: T, index: number, arr: ArrayLike<T>) => any): T | undefined {
src/vs/base/common/map.ts:
9
10 /**
11: * @deprecated ES6: use `[...SetOrMap.values()]`
12 */
13 export function values<V = any>(set: Set<V>): V[];
20
21 /**
22: * @deprecated ES6: use `[...map.keys()]`
23 */
24 export function keys<K, V>(map: Map<K, V>): K[] {
58
59 /**
60: * @deprecated ES6: use `...Map.entries()`
61 */
62 export function mapToSerializable(map: Map<string, string>): [string, string][] {
71
72 /**
73: * @deprecated ES6: use `new Map([[key1, value1],[key2, value2]])`
74 */
75 export function serializableToMap(serializable: [string, string][]): Map<string, string> {
src/vs/base/common/strings.ts:
16
17 /**
18: * @deprecated ES6: use `String.padStart`
19 */
20 export function pad(n: number, l: number, char: string = '0'): string {
147
148 /**
149: * @deprecated ES6: use `String.startsWith`
150 */
151 export function startsWith(haystack: string, needle: string): boolean {
168
169 /**
170: * @deprecated ES6: use `String.endsWith`
171 */
172 export function endsWith(haystack: string, needle: string): boolean {

View file

@ -69,5 +69,8 @@
"msjsdiag.debugger-for-chrome": "workspace"
},
"gulp.autoDetect": "off",
"files.insertFinalNewline": true
"files.insertFinalNewline": true,
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

View file

@ -135,6 +135,23 @@ steps:
displayName: Run integration tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
APP_ROOT=$(agent.builddirectory)/VSCode-darwin
APP_NAME="`ls $APP_ROOT | head -n 1`"
yarn smoketest --build "$APP_ROOT/$APP_NAME"
continueOnError: true
displayName: Run smoke tests (Electron)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \
yarn smoketest --web --headless
continueOnError: true
displayName: Run smoke tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain

View file

@ -1,7 +1,7 @@
[
{
"name": "ms-vscode.node-debug",
"version": "1.44.1",
"version": "1.44.2",
"repo": "https://github.com/Microsoft/vscode-node-debug",
"metadata": {
"id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6",

View file

@ -122,6 +122,10 @@
"name": "vs/workbench/contrib/preferences",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/quickaccess",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/quickopen",
"project": "vscode-workbench"
@ -262,6 +266,10 @@
"name": "vs/workbench/services/files",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/log",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/integrity",
"project": "vscode-workbench"

View file

@ -43,7 +43,7 @@
"minimist": "^1.2.0",
"request": "^2.85.0",
"terser": "4.3.8",
"typescript": "^3.9.0-dev.20200229",
"typescript": "3.9.0-dev.20200304",
"vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.5.4",
"xml2js": "^0.4.17"

View file

@ -2453,16 +2453,16 @@ typed-rest-client@^0.9.0:
tunnel "0.0.4"
underscore "1.8.3"
typescript@3.9.0-dev.20200304:
version "3.9.0-dev.20200304"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed"
integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q==
typescript@^3.0.1:
version "3.5.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^3.9.0-dev.20200229:
version "3.9.0-dev.20200229"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200229.tgz#45f0821d5c420a4c7d6d894c64531e1301dfa9bd"
integrity sha512-DtSLzxoiUir0qRc3+JJBxiAe6NvTEM3uDxnPxVWJU6sRDhUi8Ssx6DBjGWCZAQJlLk5A+jk2ptf3JvvZrQlLNQ==
typical@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"

View file

@ -32,7 +32,12 @@ export function updateEmmetExtensionsPath() {
let extensionsPath = vscode.workspace.getConfiguration('emmet')['extensionsPath'];
if (_currentExtensionsPath !== extensionsPath) {
_currentExtensionsPath = extensionsPath;
_emmetHelper.updateExtensionsPath(extensionsPath, vscode.workspace.rootPath).then(null, (err: string) => vscode.window.showErrorMessage(err));
if (!vscode.workspace.workspaceFolders) {
return;
} else {
const rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
_emmetHelper.updateExtensionsPath(extensionsPath, rootPath).then(null, (err: string) => vscode.window.showErrorMessage(err));
}
}
}
@ -622,4 +627,4 @@ export function trimQuotes(s: string) {
}
return s;
}
}

View file

@ -35,9 +35,13 @@
"url": "vscode://schemas/language-configuration"
},
{
"fileMatch": "*icon-theme.json",
"fileMatch": ["*icon-theme.json", "!*product-icon-theme.json"],
"url": "vscode://schemas/icon-theme"
},
{
"fileMatch": "*product-icon-theme.json",
"url": "vscode://schemas/product-icon-theme"
},
{
"fileMatch": "*color-theme.json",
"url": "vscode://schemas/color-theme"

View file

@ -20,9 +20,7 @@ function main() {
}
}
if (Object.keys(content).length > 0) {
fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content));
}
fs.writeFileSync(path.join(__dirname, '../src/common/config.json'), JSON.stringify(content));
}
main();

View file

@ -40,8 +40,13 @@ export interface ISchemaAssociations {
[pattern: string]: string[];
}
export interface ISchemaAssociation {
fileMatch: string[];
uri: string;
}
namespace SchemaAssociationNotification {
export const type: NotificationType<ISchemaAssociations, any> = new NotificationType('json/schemaAssociations');
export const type: NotificationType<ISchemaAssociations | ISchemaAssociation[], any> = new NotificationType('json/schemaAssociations');
}
namespace ResultLimitReachedNotification {
@ -264,10 +269,10 @@ export function activate(context: ExtensionContext) {
toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand));
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context));
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
extensions.onDidChange(_ => {
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context));
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
});
// manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652.
@ -324,8 +329,8 @@ export function deactivate(): Promise<any> {
return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null);
}
function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations {
const associations: ISchemaAssociations = {};
function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] {
const associations: ISchemaAssociation[] = [];
extensions.all.forEach(extension => {
const packageJSON = extension.packageJSON;
if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation) {
@ -333,23 +338,21 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations {
if (Array.isArray(jsonValidation)) {
jsonValidation.forEach(jv => {
let { fileMatch, url } = jv;
if (fileMatch && url) {
if (url[0] === '.' && url[1] === '/') {
url = Uri.file(path.join(extension.extensionPath, url)).toString();
}
if (fileMatch[0] === '%') {
fileMatch = fileMatch.replace(/%APP_SETTINGS_HOME%/, '/User');
fileMatch = fileMatch.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine');
fileMatch = fileMatch.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces');
} else if (fileMatch.charAt(0) !== '/' && !fileMatch.match(/\w+:\/\//)) {
fileMatch = '/' + fileMatch;
}
let association = associations[fileMatch];
if (!association) {
association = [];
associations[fileMatch] = association;
}
association.push(url);
if (typeof fileMatch === 'string') {
fileMatch = [fileMatch];
}
if (Array.isArray(fileMatch) && url) {
fileMatch = fileMatch.map(fm => {
if (fm[0] === '%') {
fm = fm.replace(/%APP_SETTINGS_HOME%/, '/User');
fm = fm.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine');
fm = fm.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces');
} else if (!fm.match(/^(\w+:\/\/|\/|!)/)) {
fm = '/' + fm;
}
return fm;
});
associations.push({ fileMatch, uri: url });
}
});
}

View file

@ -23,8 +23,13 @@ interface ISchemaAssociations {
[pattern: string]: string[];
}
interface ISchemaAssociation {
fileMatch: string[];
uri: string;
}
namespace SchemaAssociationNotification {
export const type: NotificationType<ISchemaAssociations, any> = new NotificationType('json/schemaAssociations');
export const type: NotificationType<ISchemaAssociations | ISchemaAssociation[], any> = new NotificationType('json/schemaAssociations');
}
namespace VSCodeContentRequest {
@ -230,7 +235,7 @@ namespace LimitExceededWarnings {
}
let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined;
let schemaAssociations: ISchemaAssociations | undefined = undefined;
let schemaAssociations: ISchemaAssociations | ISchemaAssociation[] | undefined = undefined;
let formatterRegistration: Thenable<Disposable> | null = null;
// The settings have changed. Is send on server activation as well.
@ -291,12 +296,16 @@ function updateConfiguration() {
schemas: new Array<SchemaConfiguration>()
};
if (schemaAssociations) {
for (const pattern in schemaAssociations) {
const association = schemaAssociations[pattern];
if (Array.isArray(association)) {
association.forEach(uri => {
languageSettings.schemas.push({ uri, fileMatch: [pattern] });
});
if (Array.isArray(schemaAssociations)) {
Array.prototype.push.apply(languageSettings.schemas, schemaAssociations);
} else {
for (const pattern in schemaAssociations) {
const association = schemaAssociations[pattern];
if (Array.isArray(association)) {
association.forEach(uri => {
languageSettings.schemas.push({ uri, fileMatch: [pattern] });
});
}
}
}
}

View file

@ -457,7 +457,10 @@ export class DynamicMarkdownPreview extends Disposable {
const folder = vscode.workspace.getWorkspaceFolder(base);
if (folder) {
baseRoots.push(folder.uri);
const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri);
if (workspaceRoots) {
baseRoots.push(...workspaceRoots);
}
} else if (!base.scheme || base.scheme === 'file') {
baseRoots.push(vscode.Uri.file(path.dirname(base.fsPath)));
}

View file

@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
const localize = nls.loadMessageBundle();
@ -33,11 +34,26 @@ const directives: Directive[] = [
}
];
const directives390: Directive[] = [
...directives,
{
value: '@ts-expect-error',
description: localize(
'ts-expect-error',
"Suppresses @ts-check errors on the next line of a file, expecting at least one to exist.")
}
];
class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvider {
private readonly directives: Directive[];
constructor(
private readonly client: ITypeScriptServiceClient,
) { }
) {
this.directives = client.apiVersion.gte(API.v390)
? directives390
: directives;
}
public provideCompletionItems(
document: vscode.TextDocument,
@ -53,7 +69,7 @@ class DirectiveCommentCompletionProvider implements vscode.CompletionItemProvide
const prefix = line.slice(0, position.character);
const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/);
if (match) {
return directives.map(directive => {
return this.directives.map(directive => {
const item = new vscode.CompletionItem(directive.value, vscode.CompletionItemKind.Snippet);
item.detail = directive.description;
item.range = new vscode.Range(position.line, Math.max(0, position.character - (match[1] ? match[1].length : 0)), position.line, position.character);

View file

@ -8,7 +8,6 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService';
import API from '../utils/api';
import * as typeConverters from '../utils/typeConverters';
import FileConfigurationManager from './fileConfigurationManager';
@ -26,7 +25,7 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
token: vscode.CancellationToken
): Promise<vscode.Range | null> {
const response = await this.execRename(document, position, token);
if (!response || response.type !== 'response' || !response.body) {
if (response?.type !== 'response' || !response.body) {
return null;
}
@ -35,11 +34,9 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
return Promise.reject<vscode.Range>(renameInfo.localizedErrorMessage);
}
if (this.client.apiVersion.gte(API.v310)) {
const triggerSpan = renameInfo.triggerSpan;
if (triggerSpan) {
return typeConverters.Range.fromTextSpan(triggerSpan);
}
const triggerSpan = renameInfo.triggerSpan; // added in TS 3.1
if (triggerSpan) {
return typeConverters.Range.fromTextSpan(triggerSpan);
}
return null;
@ -61,17 +58,15 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
return Promise.reject<vscode.WorkspaceEdit>(renameInfo.localizedErrorMessage);
}
if (this.client.apiVersion.gte(API.v310)) {
if (renameInfo.fileToRename) {
const edits = await this.renameFile(renameInfo.fileToRename, newName, token);
if (edits) {
return edits;
} else {
return Promise.reject<vscode.WorkspaceEdit>(localize('fileRenameFail', "An error occurred while renaming file"));
}
if (renameInfo.fileToRename) {
const edits = await this.renameFile(renameInfo.fileToRename, newName, token);
if (edits) {
return edits;
} else {
return Promise.reject<vscode.WorkspaceEdit>(localize('fileRenameFail', "An error occurred while renaming file"));
}
}
return this.updateLocs(response.body.locs, newName);
}
@ -104,11 +99,9 @@ class TypeScriptRenameProvider implements vscode.RenameProvider {
const edit = new vscode.WorkspaceEdit();
for (const spanGroup of locations) {
const resource = this.client.toResource(spanGroup.file);
if (resource) {
for (const textSpan of spanGroup.locs as Proto.RenameTextSpan[]) {
edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan),
(textSpan.prefixText || '') + newName + (textSpan.suffixText || ''));
}
for (const textSpan of spanGroup.locs) {
edit.replace(resource, typeConverters.Range.fromTextSpan(textSpan),
(textSpan.prefixText || '') + newName + (textSpan.suffixText || ''));
}
}
return edit;

View file

@ -44,7 +44,8 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide
const args: Proto.NavtoRequestArgs = {
file: filepath,
searchValue: search
searchValue: search,
maxResultCount: 256,
};
const response = await this.client.execute('navto', args, token);
@ -52,18 +53,12 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide
return [];
}
const result: vscode.SymbolInformation[] = [];
for (const item of response.body) {
if (!item.containerName && item.kind === 'alias') {
continue;
}
const label = TypeScriptWorkspaceSymbolProvider.getLabel(item);
result.push(new vscode.SymbolInformation(label, getSymbolKind(item), item.containerName || '',
typeConverters.Location.fromTextSpan(this.client.toResource(item.file), item)));
}
return result;
return response.body
.filter(item => item.containerName && item.kind !== 'alias')
.map(item => this.toSymbolInformation(item));
}
private async toOpenedFiledPath(document: vscode.TextDocument) {
if (document.uri.scheme === fileSchemes.git) {
try {
@ -79,6 +74,15 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide
return this.client.toOpenedFilePath(document);
}
private toSymbolInformation(item: Proto.NavtoItem) {
const label = TypeScriptWorkspaceSymbolProvider.getLabel(item);
return new vscode.SymbolInformation(
label,
getSymbolKind(item),
item.containerName || '',
typeConverters.Location.fromTextSpan(this.client.toResource(item.file), item));
}
private static getLabel(item: Proto.NavtoItem) {
const label = item.name;
if (item.kind === 'method' || item.kind === 'function') {

View file

@ -872,7 +872,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
}
function getReportIssueArgsForError(error: TypeScriptServerError): { issueTitle: string, issueBody: string } | undefined {
function getReportIssueArgsForError(error: TypeScriptServerError): { extensionId: string, issueTitle: string, issueBody: string } | undefined {
if (!error.serverStack || !error.serverMessage) {
return undefined;
}
@ -880,6 +880,7 @@ function getReportIssueArgsForError(error: TypeScriptServerError): { issueTitle:
// Note these strings are intentionally not localized
// as we want users to file issues in english
return {
extensionId: 'vscode.typescript-language-features',
issueTitle: `TS Server fatal error: ${error.serverMessage}`,
issueBody: `**TypeScript Version:** ${error.version.apiVersion?.fullVersionString}

View file

@ -33,6 +33,7 @@ export default class API {
public static readonly v350 = API.fromSimpleString('3.5.0');
public static readonly v380 = API.fromSimpleString('3.8.0');
public static readonly v381 = API.fromSimpleString('3.8.1');
public static readonly v390 = API.fromSimpleString('3.9.0');
public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString);

View file

@ -55,6 +55,14 @@
"fontStyle": "bold"
}
}
],
"productIconThemes": [
{
"id": "Test Product Icons",
"label": "The Test Product Icon Theme",
"path": "./producticons/test-product-icon-theme.json",
"_watch": true
}
]
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) <2013> <Elegant Themes, Inc.>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,43 @@
{
// ElegantIcons from https://www.elegantthemes.com/icons/elegant_font.zip
"fonts": [
{
"id": "elegant",
"src": [
{
"path": "./ElegantIcons.woff",
"format": "woff"
}
],
"weight": "normal",
"style": "normal",
}
],
"iconDefinitions": {
"chevron-down": {
"fontCharacter": "\\43",
},
"chevron-right": {
"fontCharacter": "\\45"
},
"error": {
"fontCharacter": "\\e062"
},
"warning": {
"fontCharacter": "\\e063"
},
"settings-gear": {
"fontCharacter": "\\e030"
},
"files": {
"fontCharacter": "\\e056"
},
"extensions": {
"fontCharacter": "\\e015"
},
"debug-alt-2": {
"fontCharacter": "\\e072"
}
}
}

View file

@ -150,7 +150,7 @@
"source-map": "^0.4.4",
"style-loader": "^1.0.0",
"ts-loader": "^4.4.2",
"typescript": "^3.9.0-dev.20200229",
"typescript": "3.9.0-dev.20200304",
"typescript-formatter": "7.1.0",
"underscore": "^1.8.2",
"vinyl": "^2.0.0",

View file

@ -8,6 +8,9 @@
const bootstrap = require('./bootstrap');
// Remove global paths from the node module lookup
bootstrap.removeGlobalNodeModuleLookupPaths();
// Enable ASAR in our forked processes
bootstrap.enableASARSupport();

23
src/bootstrap.js vendored
View file

@ -53,6 +53,29 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
};
//#endregion
//#region Remove global paths from the node lookup paths
exports.removeGlobalNodeModuleLookupPaths = function() {
// @ts-ignore
const Module = require('module');
// @ts-ignore
const globalPaths = Module.globalPaths;
// @ts-ignore
const originalResolveLookupPaths = Module._resolveLookupPaths;
// @ts-ignore
Module._resolveLookupPaths = function (moduleName, parent) {
const paths = originalResolveLookupPaths(moduleName, parent);
let commonSuffixLength = 0;
while (commonSuffixLength < paths.length && paths[paths.length - 1 - commonSuffixLength] === globalPaths[globalPaths.length - 1 - commonSuffixLength]) {
commonSuffixLength++;
}
return paths.slice(0, paths.length - commonSuffixLength);
};
};
//#endregion
//#region Add support for using node_modules.asar
/**
* @param {string=} nodeModulesPath

View file

@ -25,7 +25,7 @@
flex-direction: column-reverse;
width: min-content;
min-width: 500px;
max-width: 90%;
max-width: 90vw;
min-height: 75px;
padding: 10px;
transform: translate3d(0px, 0px, 0px);

View file

@ -248,6 +248,10 @@ export class InputBox extends Widget {
}
}
public getAriaLabel(): string {
return this.ariaLabel;
}
public get mirrorElement(): HTMLElement | undefined {
return this.mirror;
}

View file

@ -180,16 +180,16 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
}
}
class FocusTrait<T> extends Trait<T> {
class SelectionTrait<T> extends Trait<T> {
constructor(private isAriaSelected: (index: number) => boolean) {
super('focused');
constructor() {
super('selected');
}
renderIndex(index: number, container: HTMLElement): void {
super.renderIndex(index, container);
if (this.contains(index) || this.isAriaSelected(index)) {
if (this.contains(index)) {
container.setAttribute('aria-selected', 'true');
} else {
container.setAttribute('aria-selected', 'false');
@ -1198,8 +1198,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
renderers: IListRenderer<any /* TODO@joao */, any>[],
private _options: IListOptions<T> = DefaultOptions
) {
this.selection = new Trait('selected');
this.focus = new FocusTrait(this.selection.contains);
this.selection = new SelectionTrait();
this.focus = new Trait('focused');
mixin(_options, defaultStyles, false);

View file

@ -15,12 +15,13 @@ import { Color, RGBA } from 'vs/base/common/color';
import { SplitView, IView } from './splitview';
import { isFirefox } from 'vs/base/browser/browser';
import { DataTransfers } from 'vs/base/browser/dnd';
import { localize } from 'vs/nls';
export interface IPaneOptions {
ariaHeaderLabel?: string;
minimumBodySize?: number;
maximumBodySize?: number;
expanded?: boolean;
title: string;
}
export interface IPaneStyles {
@ -116,10 +117,10 @@ export abstract class Pane extends Disposable implements IView {
width: number = 0;
constructor(options: IPaneOptions = {}) {
constructor(options: IPaneOptions) {
super();
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title);
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;

View file

@ -285,15 +285,15 @@ export interface IItemAccessor<T> {
/**
* Just the label of the item to score on.
*/
getItemLabel(item: T): string | null;
getItemLabel(item: T): string | undefined;
/**
* The optional description of the item to score on. Can be null.
* The optional description of the item to score on.
*/
getItemDescription(item: T): string | null;
getItemDescription(item: T): string | undefined;
/**
* If the item is a file, the path of the file to score on. Can be null.
* If the item is a file, the path of the file to score on.
*/
getItemPath(file: T): string | undefined;
}
@ -311,7 +311,7 @@ export interface IPreparedQuery {
}
/**
* Helper function to prepare a search value for scoring in quick open by removing unwanted characters.
* Helper function to prepare a search value for scoring by removing unwanted characters.
*/
export function prepareQuery(original: string): IPreparedQuery {
if (!original) {
@ -364,6 +364,7 @@ function createMatches(offsets: undefined | number[]): IMatch[] {
if (!offsets) {
return ret;
}
let last: IMatch | undefined;
for (const pos of offsets) {
if (last && last.end === pos) {
@ -373,10 +374,11 @@ function createMatches(offsets: undefined | number[]): IMatch[] {
ret.push(last);
}
}
return ret;
}
function doScoreItem(label: string, description: string | null, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
// 1.) treat identity matches on full path highest
if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) {
@ -589,7 +591,7 @@ function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number
return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1;
}
export function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
// check for label + description length and prefer shorter
const labelA = accessor.getItemLabel(itemA) || '';

View file

@ -142,6 +142,7 @@ class QuickInput extends Disposable implements IQuickInput {
private buttonsUpdated = false;
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
private readonly onDidHideEmitter = this._register(new Emitter<void>());
private readonly onDisposeEmitter = this._register(new Emitter<void>());
protected readonly visibleDisposables = this._register(new DisposableStore());
@ -235,7 +236,7 @@ class QuickInput extends Disposable implements IQuickInput {
this.update();
}
onDidTriggerButton = this.onDidTriggerButtonEmitter.event;
readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event;
show(): void {
if (this.visible) {
@ -266,7 +267,7 @@ class QuickInput extends Disposable implements IQuickInput {
this.onDidHideEmitter.fire();
}
onDidHide = this.onDidHideEmitter.event;
readonly onDidHide = this.onDidHideEmitter.event;
protected update() {
if (!this.visible) {
@ -298,9 +299,8 @@ class QuickInput extends Disposable implements IQuickInput {
this.ui.leftActionBar.clear();
const leftButtons = this.buttons.filter(button => button === backButton);
this.ui.leftActionBar.push(leftButtons.map((button, index) => {
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => {
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => {
this.onDidTriggerButtonEmitter.fire(button);
return Promise.resolve(null);
});
action.tooltip = button.tooltip || '';
return action;
@ -308,9 +308,8 @@ class QuickInput extends Disposable implements IQuickInput {
this.ui.rightActionBar.clear();
const rightButtons = this.buttons.filter(button => button !== backButton);
this.ui.rightActionBar.push(rightButtons.map((button, index) => {
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => {
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => {
this.onDidTriggerButtonEmitter.fire(button);
return Promise.resolve(null);
});
action.tooltip = button.tooltip || '';
return action;
@ -362,17 +361,22 @@ class QuickInput extends Disposable implements IQuickInput {
}
}
readonly onDispose = this.onDisposeEmitter.event;
public dispose(): void {
this.hide();
this.onDisposeEmitter.fire();
super.dispose();
}
}
class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPick<T> {
private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
private _value = '';
private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL;
private _placeholder: string | undefined;
private readonly onDidChangeValueEmitter = this._register(new Emitter<string>());
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
@ -404,7 +408,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
quickNavigate: IQuickNavigateConfiguration | undefined;
get value() {
return this._value;
}
@ -414,6 +417,17 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
filterValue = (value: string) => value;
set ariaLabel(ariaLabel: string) {
this._ariaLabel = ariaLabel || QuickPick.DEFAULT_ARIA_LABEL;
this.update();
}
get ariaLabel() {
return this._ariaLabel;
}
get placeholder() {
return this._placeholder;
}
@ -599,7 +613,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
return;
}
this._value = value;
this.ui.list.filter(this.ui.inputBox.value);
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
this.trySelectFirst();
this.onDidChangeValueEmitter.fire(value);
}));
@ -770,10 +784,17 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
this.ui.inputBox.placeholder = (this.placeholder || '');
}
if (this.ui.inputBox.ariaLabel !== this.ariaLabel) {
this.ui.inputBox.ariaLabel = this.ariaLabel;
}
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.list.sortByLabel = this.sortByLabel;
if (this.itemsUpdated) {
this.itemsUpdated = false;
this.ui.list.setElements(this.items);
this.ui.list.filter(this.ui.inputBox.value);
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
this.ui.count.setCount(this.ui.list.getCheckedCount());
@ -815,12 +836,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
}
this.ui.customButton.label = this.customLabel || '';
this.ui.customButton.element.title = this.customHover || '';
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.list.sortByLabel = this.sortByLabel;
this.ui.setComboboxAccessibility(true);
this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL);
}
}
@ -1378,7 +1394,7 @@ export class QuickInputController extends Disposable {
ui.list.sortByLabel = true;
ui.ignoreFocusOut = false;
this.setComboboxAccessibility(false);
ui.inputBox.removeAttribute('aria-label');
ui.inputBox.ariaLabel = '';
const backKeybindingLabel = this.options.backKeybindingLabel();
backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back");
@ -1472,19 +1488,16 @@ export class QuickInputController extends Disposable {
}
}
accept() {
async accept() {
this.onDidAcceptEmitter.fire();
return Promise.resolve(undefined);
}
back() {
async back() {
this.onDidTriggerButtonEmitter.fire(this.backButton);
return Promise.resolve(undefined);
}
cancel() {
async cancel() {
this.hide();
return Promise.resolve(undefined);
}
layout(dimension: dom.IDimension, titleBarOffset: number): void {

View file

@ -66,6 +66,14 @@ export class QuickInputBox extends Disposable {
this.inputBox.setPlaceHolder(placeholder);
}
get ariaLabel() {
return this.inputBox.getAriaLabel();
}
set ariaLabel(ariaLabel: string) {
this.inputBox.setAriaLabel(ariaLabel);
}
get password() {
return this.inputBox.inputElement.type === 'password';
}

View file

@ -33,8 +33,12 @@ interface IListElement {
readonly index: number;
readonly item: IQuickPickItem;
readonly saneLabel: string;
readonly saneAriaLabel: string;
readonly saneDescription?: string;
readonly saneDetail?: string;
readonly labelHighlights?: IMatch[];
readonly descriptionHighlights?: IMatch[];
readonly detailHighlights?: IMatch[];
readonly checked: boolean;
readonly separator?: IQuickPickSeparator;
readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
@ -44,6 +48,7 @@ class ListElement implements IListElement {
index!: number;
item!: IQuickPickItem;
saneLabel!: string;
saneAriaLabel!: string;
saneDescription?: string;
saneDetail?: string;
hidden = false;
@ -142,16 +147,14 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
options.descriptionTitle = element.saneDescription;
options.descriptionMatches = descriptionHighlights || [];
options.extraClasses = element.item.iconClasses;
options.italic = element.item.italic;
data.label.setLabel(element.saneLabel, element.saneDescription, options);
// Meta
data.detail.set(element.saneDetail, detailHighlights);
// ARIA label
data.entry.setAttribute('aria-label', [element.saneLabel, element.saneDescription, element.saneDetail]
.map(s => s && parseCodicons(s).text)
.filter(s => !!s)
.join(', '));
data.entry.setAttribute('aria-label', element.saneAriaLabel);
// Separator
if (element.separator && element.separator.label) {
@ -364,12 +367,24 @@ export class QuickInputList {
this.elements = inputElements.reduce((result, item, index) => {
if (item.type !== 'separator') {
const previous = index && inputElements[index - 1];
const saneLabel = item.label && item.label.replace(/\r?\n/g, ' ');
const saneDescription = item.description && item.description.replace(/\r?\n/g, ' ');
const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' ');
const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail]
.map(s => s && parseCodicons(s).text)
.filter(s => !!s)
.join(', ');
result.push(new ListElement({
index,
item,
saneLabel: item.label && item.label.replace(/\r?\n/g, ' '),
saneDescription: item.description && item.description.replace(/\r?\n/g, ' '),
saneDetail: item.detail && item.detail.replace(/\r?\n/g, ' '),
saneLabel,
saneAriaLabel,
saneDescription,
saneDetail,
labelHighlights: item.highlights?.label,
descriptionHighlights: item.highlights?.description,
detailHighlights: item.highlights?.detail,
checked: false,
separator: previous && previous.type === 'separator' ? previous : undefined,
fireButtonTriggered
@ -472,6 +487,7 @@ export class QuickInputList {
filter(query: string) {
if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
this.list.layout();
return;
}
query = query.trim();

View file

@ -6,14 +6,25 @@
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IMatch } from 'vs/base/common/filters';
export interface IQuickPickItemHighlights {
label?: IMatch[];
description?: IMatch[];
detail?: IMatch[];
}
export interface IQuickPickItem {
type?: 'item';
id?: string;
label: string;
ariaLabel?: string;
description?: string;
detail?: string;
iconClasses?: string[];
italic?: boolean;
highlights?: IQuickPickItemHighlights;
buttons?: IQuickInputButton[];
picked?: boolean;
alwaysShow?: boolean;
@ -125,7 +136,10 @@ export interface IInputOptions {
validateInput?: (input: string) => Promise<string | null | undefined>;
}
export interface IQuickInput {
export interface IQuickInput extends IDisposable {
readonly onDidHide: Event<void>;
readonly onDispose: Event<void>;
title: string | undefined;
@ -146,16 +160,20 @@ export interface IQuickInput {
show(): void;
hide(): void;
onDidHide: Event<void>;
dispose(): void;
}
export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
value: string;
/**
* A method that allows to massage the value used
* for filtering, e.g, to remove certain parts.
*/
filterValue: (value: string) => string;
ariaLabel: string;
placeholder: string | undefined;
readonly onDidChangeValue: Event<string>;

View file

@ -17,7 +17,7 @@ import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidge
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
import { OS } from 'vs/base/common/platform';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { IItemAccessor } from 'vs/base/common/fuzzyScorer';
import { coalesce } from 'vs/base/common/arrays';
import { IMatch } from 'vs/base/common/filters';
@ -35,12 +35,12 @@ let IDS = 0;
export class QuickOpenItemAccessorClass implements IItemAccessor<QuickOpenEntry> {
getItemLabel(entry: QuickOpenEntry): string | null {
return types.withUndefinedAsNull(entry.getLabel());
getItemLabel(entry: QuickOpenEntry): string | undefined {
return entry.getLabel();
}
getItemDescription(entry: QuickOpenEntry): string | null {
return types.withUndefinedAsNull(entry.getDescription());
getItemDescription(entry: QuickOpenEntry): string | undefined {
return entry.getDescription();
}
getItemPath(entry: QuickOpenEntry): string | undefined {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer';
import * as scorer from 'vs/base/common/fuzzyScorer';
import { URI } from 'vs/base/common/uri';
import { basename, dirname, sep } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
@ -49,14 +49,14 @@ function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: scorer.I
return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache);
}
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache, fallbackComparer = scorer.fallbackCompare): number {
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer);
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache, fallbackComparer?: (itemA: T, itemB: T, query: scorer.IPreparedQuery, accessor: scorer.IItemAccessor<T>) => number): number {
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer as any);
}
const NullAccessor = new NullAccessorClass();
let cache: scorer.ScorerCache = Object.create(null);
suite('Quick Open Scorer', () => {
suite('Fuzzy Scorer', () => {
setup(() => {
cache = Object.create(null);

View file

@ -81,6 +81,8 @@ export class IssueReporter extends Disposable {
this.initServices(configuration);
const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION;
const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined;
this.issueReporterModel = new IssueReporterModel({
issueType: configuration.data.issueType || IssueType.Bug,
versionInfo: {
@ -88,8 +90,8 @@ export class IssueReporter extends Disposable {
os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`
},
extensionsDisabled: !!this.environmentService.disableExtensions,
fileOnExtension: configuration.data.extensionId ? true : undefined,
selectedExtension: configuration.data.extensionId ? configuration.data.enabledExtensions.filter(extension => extension.id === configuration.data.extensionId)[0] : undefined
fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined,
selectedExtension: targetExtension,
});
const issueReporterElement = this.getElementById('issue-reporter');
@ -260,19 +262,20 @@ export class IssueReporter extends Disposable {
}
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
const { nonThemes, themes } = collections.groupBy(extensions, ext => {
const installedExtensions = extensions.filter(x => !x.isBuiltin);
const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => {
return ext.isTheme ? 'themes' : 'nonThemes';
});
const numberOfThemeExtesions = themes && themes.length;
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: extensions });
this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions });
this.updateExtensionTable(nonThemes, numberOfThemeExtesions);
if (this.environmentService.disableExtensions || extensions.length === 0) {
if (this.environmentService.disableExtensions || installedExtensions.length === 0) {
(<HTMLButtonElement>this.getElementById('disableExtensions')).disabled = true;
}
this.updateExtensionSelector(extensions);
this.updateExtensionSelector(installedExtensions);
}
private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void {
@ -748,10 +751,14 @@ export class IssueReporter extends Disposable {
private setSourceOptions(): void {
const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement;
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData();
let selected = sourceSelect.selectedIndex;
if (selected === -1 && fileOnExtension !== undefined) {
selected = fileOnExtension ? 2 : 1;
if (selected === -1) {
if (fileOnExtension !== undefined) {
selected = fileOnExtension ? 2 : 1;
} else if (selectedExtension?.isBuiltin) {
selected = 1;
}
}
sourceSelect.innerHTML = '';

View file

@ -49,10 +49,10 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
@ -67,6 +67,7 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@ -194,6 +195,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
@ -223,6 +225,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const userDataSyncStoreServiceChannel = new UserDataSyncStoreServiceChannel(userDataSyncStoreService);
server.registerChannel('userDataSyncStoreService', userDataSyncStoreServiceChannel);
const userDataSyncBackupStoreService = accessor.get(IUserDataSyncBackupStoreService);
const userDataSyncBackupStoreServiceChannel = new UserDataSyncBackupStoreServiceChannel(userDataSyncBackupStoreService);
server.registerChannel('userDataSyncBackupStoreService', userDataSyncBackupStoreServiceChannel);
const settingsSyncService = accessor.get(ISettingsSyncService);
const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService);
server.registerChannel('settingsSync', settingsSyncChannel);

View file

@ -392,6 +392,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.setFullScreen(false);
this.setFullScreen(true);
}
this.sendWhenReady('vscode:displayChanged');
}, 100));
const displayChangedListener = () => simpleFullScreenScheduler.schedule();

View file

@ -856,18 +856,13 @@ export namespace CoreNavigationCommands {
}
}));
export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
constructor() {
super({
id: 'cursorLineStart',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A }
}
});
class LineStartCommand extends CoreEditorCommand {
private readonly _inSelectionMode: boolean;
constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) {
super(opts);
this._inSelectionMode = opts.inSelectionMode;
}
public runCoreEditorCommand(cursors: ICursors, args: any): void {
@ -885,11 +880,35 @@ export namespace CoreNavigationCommands {
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const lineNumber = cursor.modelState.position.lineNumber;
result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, 1, 0));
result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, 1, 0));
}
return result;
}
});
}
export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new LineStartCommand({
inSelectionMode: false,
id: 'cursorLineStart',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A }
}
}));
export const CursorLineStartSelect: CoreEditorCommand = registerEditorCommand(new LineStartCommand({
inSelectionMode: true,
id: 'cursorLineStartSelect',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_A }
}
}));
class EndCommand extends CoreEditorCommand {
@ -935,18 +954,13 @@ export namespace CoreNavigationCommands {
}
}));
export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
constructor() {
super({
id: 'cursorLineEnd',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E }
}
});
class LineEndCommand extends CoreEditorCommand {
private readonly _inSelectionMode: boolean;
constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) {
super(opts);
this._inSelectionMode = opts.inSelectionMode;
}
public runCoreEditorCommand(cursors: ICursors, args: any): void {
@ -965,11 +979,35 @@ export namespace CoreNavigationCommands {
const cursor = cursors[i];
const lineNumber = cursor.modelState.position.lineNumber;
const maxColumn = context.model.getLineMaxColumn(lineNumber);
result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, maxColumn, 0));
result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, maxColumn, 0));
}
return result;
}
});
}
export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new LineEndCommand({
inSelectionMode: false,
id: 'cursorLineEnd',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E }
}
}));
export const CursorLineEndSelect: CoreEditorCommand = registerEditorCommand(new LineEndCommand({
inSelectionMode: true,
id: 'cursorLineEndSelect',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: 0,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_E }
}
}));
class TopCommand extends CoreEditorCommand {

View file

@ -925,6 +925,23 @@ export class MouseTargetFactory {
}
}
// For inline decorations, Gecko returns the `<span>` of the line and the offset is the `<span>` with the inline decoration
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.ELEMENT_NODE) {
const parent1 = hitResult.offsetNode.parentNode; // expected to be the view line div
const parent1ClassName = parent1 && parent1.nodeType === parent1.ELEMENT_NODE ? (<HTMLElement>parent1).className : null;
if (parent1ClassName === ViewLine.CLASS_NAME) {
const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)];
if (tokenSpan) {
const p = ctx.getPositionFromDOMInfo(<HTMLElement>tokenSpan, 0);
return {
position: p,
hitTarget: null
};
}
}
}
return {
position: null,
hitTarget: <HTMLElement>hitResult.offsetNode

View file

@ -252,9 +252,9 @@ export class TextAreaHandler extends ViewPart {
this._viewController.setSelection('keyboard', modelSelection);
}));
this._register(this._textAreaInput.onCompositionStart(() => {
this._register(this._textAreaInput.onCompositionStart((e) => {
const lineNumber = this._selections[0].startLineNumber;
const column = this._selections[0].startColumn;
const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0);
this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
'keyboard',

View file

@ -95,6 +95,10 @@ class InMemoryClipboardMetadataManager {
}
}
export interface ICompositionStartEvent {
moveOneCharacterLeft: boolean;
}
/**
* Writes screen reader content to the textarea and is able to analyze its input events to generate:
* - onCut
@ -126,8 +130,8 @@ export class TextAreaInput extends Disposable {
private _onType = this._register(new Emitter<ITypeData>());
public readonly onType: Event<ITypeData> = this._onType.event;
private _onCompositionStart = this._register(new Emitter<void>());
public readonly onCompositionStart: Event<void> = this._onCompositionStart.event;
private _onCompositionStart = this._register(new Emitter<ICompositionStartEvent>());
public readonly onCompositionStart: Event<ICompositionStartEvent> = this._onCompositionStart.event;
private _onCompositionUpdate = this._register(new Emitter<ICompositionData>());
public readonly onCompositionUpdate: Event<ICompositionData> = this._onCompositionUpdate.event;
@ -165,9 +169,11 @@ export class TextAreaInput extends Disposable {
this._isDoingComposition = false;
this._nextCommand = ReadFromTextArea.Type;
let lastKeyDown: IKeyboardEvent | null = null;
this._register(dom.addStandardDisposableListener(textArea.domNode, 'keydown', (e: IKeyboardEvent) => {
if (this._isDoingComposition &&
(e.keyCode === KeyCode.KEY_IN_COMPOSITION || e.keyCode === KeyCode.Backspace)) {
if (e.keyCode === KeyCode.KEY_IN_COMPOSITION
|| (this._isDoingComposition && e.keyCode === KeyCode.Backspace)) {
// Stop propagation for keyDown events if the IME is processing key input
e.stopPropagation();
}
@ -177,6 +183,8 @@ export class TextAreaInput extends Disposable {
// See https://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx
e.preventDefault();
}
lastKeyDown = e;
this._onKeyDown.fire(e);
}));
@ -190,12 +198,35 @@ export class TextAreaInput extends Disposable {
}
this._isDoingComposition = true;
// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
if (!browser.isEdge) {
let moveOneCharacterLeft = false;
if (
platform.isMacintosh
&& lastKeyDown
&& lastKeyDown.equals(KeyCode.KEY_IN_COMPOSITION)
&& this._textAreaState.selectionStart === this._textAreaState.selectionEnd
&& this._textAreaState.selectionStart > 0
&& this._textAreaState.value.substr(this._textAreaState.selectionStart - 1, 1) === e.data
) {
// Handling long press case on macOS + arrow key => pretend the character was selected
if (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft') {
moveOneCharacterLeft = true;
}
}
if (moveOneCharacterLeft) {
this._textAreaState = new TextAreaState(
this._textAreaState.value,
this._textAreaState.selectionStart - 1,
this._textAreaState.selectionEnd,
this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null,
this._textAreaState.selectionEndPosition
);
} else if (!browser.isEdge) {
// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
this._setAndWriteTextAreaState('compositionstart', TextAreaState.EMPTY);
}
this._onCompositionStart.fire();
this._onCompositionStart.fire({ moveOneCharacterLeft });
}));
/**

View file

@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network';
import { normalizePath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener';
import { EditorOpenContext } from 'vs/platform/editor/common/editor';
@ -28,9 +28,6 @@ class CommandOpener implements IOpener {
if (typeof target === 'string') {
target = URI.parse(target);
}
if (!CommandsRegistry.getCommand(target.path)) {
throw new Error(`command '${target.path}' NOT known`);
}
// execute as command
let args: any = [];
try {

View file

@ -779,7 +779,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
}
private _type(source: string, text: string): void {
if (!this._isDoingComposition && source === 'keyboard') {
if (source === 'keyboard') {
// If this event is coming straight from the keyboard, look for electric characters and enter
const len = text.length;
@ -790,7 +790,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
// Here we must interpret each typed character individually
const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr));
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._isDoingComposition, this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr));
offset += charLength;
}

View file

@ -91,9 +91,10 @@ export class MoveOperations {
public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition {
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
const lineCount = model.getLineCount();
const wasOnLastPosition = (lineNumber === lineCount && column === model.getLineMaxColumn(lineNumber));
lineNumber = lineNumber + count;
let lineCount = model.getLineCount();
if (lineNumber > lineCount) {
lineNumber = lineCount;
if (allowMoveOnLastLine) {
@ -105,7 +106,11 @@ export class MoveOperations {
column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn);
}
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
if (wasOnLastPosition) {
leftoverVisibleColumns = 0;
} else {
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
}
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
}
@ -144,6 +149,7 @@ export class MoveOperations {
public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition {
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
const wasOnFirstPosition = (lineNumber === 1 && column === 1);
lineNumber = lineNumber - count;
if (lineNumber < 1) {
@ -157,7 +163,11 @@ export class MoveOperations {
column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn);
}
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
if (wasOnFirstPosition) {
leftoverVisibleColumns = 0;
} else {
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
}
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
}

View file

@ -269,9 +269,15 @@ export class TypeOperations {
commands[i] = null;
continue;
}
let pos = selection.getPosition();
let startColumn = Math.max(1, pos.column - replaceCharCnt);
let range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column);
const pos = selection.getPosition();
const startColumn = Math.max(1, pos.column - replaceCharCnt);
const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column);
const oldText = model.getValueInRange(range);
if (oldText === txt) {
// => ignore composition that doesn't do anything
commands[i] = null;
continue;
}
commands[i] = new ReplaceCommand(range, txt);
}
return new EditOperationResult(EditOperationType.Typing, commands, {
@ -796,9 +802,9 @@ export class TypeOperations {
return null;
}
public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult {
public static typeWithInterceptors(isDoingComposition: boolean, prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult {
if (ch === '\n') {
if (!isDoingComposition && ch === '\n') {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = TypeOperations._enter(config, model, false, selections[i]);
@ -809,7 +815,7 @@ export class TypeOperations {
});
}
if (this._isAutoIndentType(config, model, selections)) {
if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) {
let commands: Array<ICommand | null> = [];
let autoIndentFails = false;
for (let i = 0, len = selections.length; i < len; i++) {
@ -827,13 +833,15 @@ export class TypeOperations {
}
}
if (this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) {
if (!isDoingComposition && this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) {
return this._runAutoClosingOvertype(prevEditOperationType, config, model, selections, ch);
}
const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true);
if (autoClosingPairOpenCharType) {
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType);
if (!isDoingComposition) {
const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true);
if (autoClosingPairOpenCharType) {
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType);
}
}
if (this._isSurroundSelectionType(config, model, selections, ch)) {
@ -842,7 +850,7 @@ export class TypeOperations {
// Electric characters make sense only when dealing with a single cursor,
// as multiple cursors typing brackets for example would interfer with bracket matching
if (this._isTypeInterceptorElectricChar(config, model, selections)) {
if (!isDoingComposition && this._isTypeInterceptorElectricChar(config, model, selections)) {
const r = this._typeInterceptorElectricChar(prevEditOperationType, config, model, selections[0], ch);
if (r) {
return r;

View file

@ -45,6 +45,10 @@ export namespace GoToLineNLS {
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line...");
}
export namespace QuickHelpNLS {
export const helpQuickAccessActionLabel = nls.localize('helpQuickAccess', "Show all Quick Access Providers");
}
export namespace QuickCommandNLS {
export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands");
export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands");

View file

@ -13,66 +13,43 @@ export class MoveCaretCommand implements ICommand {
private readonly _selection: Selection;
private readonly _isMovingLeft: boolean;
private _cutStartIndex: number;
private _cutEndIndex: number;
private _moved: boolean;
private _selectionId: string | null;
constructor(selection: Selection, isMovingLeft: boolean) {
this._selection = selection;
this._isMovingLeft = isMovingLeft;
this._cutStartIndex = -1;
this._cutEndIndex = -1;
this._moved = false;
this._selectionId = null;
}
public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
let s = this._selection;
this._selectionId = builder.trackSelection(s);
if (s.startLineNumber !== s.endLineNumber) {
if (this._selection.startLineNumber !== this._selection.endLineNumber || this._selection.isEmpty()) {
return;
}
if (this._isMovingLeft && s.startColumn === 0) {
return;
} else if (!this._isMovingLeft && s.endColumn === model.getLineMaxColumn(s.startLineNumber)) {
const lineNumber = this._selection.startLineNumber;
const startColumn = this._selection.startColumn;
const endColumn = this._selection.endColumn;
if (this._isMovingLeft && startColumn === 1) {
return;
}
if (!this._isMovingLeft && endColumn === model.getLineMaxColumn(lineNumber)) {
return;
}
let lineNumber = s.selectionStartLineNumber;
let lineContent = model.getLineContent(lineNumber);
let left: string;
let middle: string;
let right: string;
if (this._isMovingLeft) {
left = lineContent.substring(0, s.startColumn - 2);
middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1);
right = lineContent.substring(s.startColumn - 2, s.startColumn - 1) + lineContent.substring(s.endColumn - 1);
const rangeBefore = new Range(lineNumber, startColumn - 1, lineNumber, startColumn);
const charBefore = model.getValueInRange(rangeBefore);
builder.addEditOperation(rangeBefore, null);
builder.addEditOperation(new Range(lineNumber, endColumn, lineNumber, endColumn), charBefore);
} else {
left = lineContent.substring(0, s.startColumn - 1) + lineContent.substring(s.endColumn - 1, s.endColumn);
middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1);
right = lineContent.substring(s.endColumn);
const rangeAfter = new Range(lineNumber, endColumn, lineNumber, endColumn + 1);
const charAfter = model.getValueInRange(rangeAfter);
builder.addEditOperation(rangeAfter, null);
builder.addEditOperation(new Range(lineNumber, startColumn, lineNumber, startColumn), charAfter);
}
let newLineContent = left + middle + right;
builder.addEditOperation(new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)), null);
builder.addEditOperation(new Range(lineNumber, 1, lineNumber, 1), newLineContent);
this._cutStartIndex = s.startColumn + (this._isMovingLeft ? -1 : 1);
this._cutEndIndex = this._cutStartIndex + s.endColumn - s.startColumn;
this._moved = true;
}
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
let result = helper.getTrackedSelection(this._selectionId!);
if (this._moved) {
result = result.setStartPosition(result.startLineNumber, this._cutStartIndex);
result = result.setEndPosition(result.startLineNumber, this._cutEndIndex);
if (this._isMovingLeft) {
return new Selection(this._selection.startLineNumber, this._selection.startColumn - 1, this._selection.endLineNumber, this._selection.endColumn - 1);
} else {
return new Selection(this._selection.startLineNumber, this._selection.startColumn + 1, this._selection.endLineNumber, this._selection.endColumn + 1);
}
return result;
}
}

View file

@ -8,7 +8,7 @@ import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/err
import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { StableEditorScrollState } from 'vs/editor/browser/core/editorState';
import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { registerEditorContribution, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes';
@ -20,6 +20,7 @@ import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import * as dom from 'vs/base/browser/dom';
import { hash } from 'vs/base/common/hash';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
export class CodeLensContribution implements IEditorContribution {
@ -402,6 +403,53 @@ export class CodeLensContribution implements IEditorContribution {
}
});
}
public getLenses(): CodeLensWidget[] {
return this._lenses;
}
}
export class ShowLensesInCurrentLineCommand extends EditorCommand {
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void> {
const quickInputService = accessor.get(IQuickInputService);
const commandService = accessor.get(ICommandService);
const notificationService = accessor.get(INotificationService);
const lineNumber = editor.getSelection()?.positionLineNumber;
const codelensController = editor.getContribution(CodeLensContribution.ID) as CodeLensContribution;
const activeLensesWidgets = codelensController.getLenses().filter(lens => lens.getLineNumber() === lineNumber);
const commandArguments: Map<string, any[] | undefined> = new Map();
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
activeLensesWidgets.forEach(widget => {
widget.getItems().forEach(codelens => {
const command = codelens.symbol.command;
if (!command) {
return;
}
items.push({ id: command.id, label: command.title });
commandArguments.set(command.id, command.arguments);
});
});
// We dont want an empty picker
if (!items.length) {
return;
}
quickInputService.pick(items, { canPickMany: false }).then(item => {
const id = item.id!;
commandService.executeCommand(id, ...(commandArguments.get(id) || [])).catch(err => notificationService.error(err));
});
}
}
registerEditorContribution(CodeLensContribution.ID, CodeLensContribution);
const showLensesInCurrentLineCommand = new ShowLensesInCurrentLineCommand({ id: 'codelens.showLensesInCurrentLine', precondition: undefined });
showLensesInCurrentLineCommand.register();

View file

@ -336,6 +336,10 @@ export class CodeLensWidget {
}
}
}
getItems(): CodeLensItem[] {
return this._data;
}
}
registerThemingParticipant((theme, collector) => {

View file

@ -108,8 +108,9 @@ export class ClickLinkGesture extends Disposable {
private readonly _editor: ICodeEditor;
private _opts: ClickLinkOptions;
private lastMouseMoveEvent: ClickLinkMouseEvent | null;
private hasTriggerKeyOnMouseDown: boolean;
private _lastMouseMoveEvent: ClickLinkMouseEvent | null;
private _hasTriggerKeyOnMouseDown: boolean;
private _lineNumberOnMouseDown: number;
constructor(editor: ICodeEditor) {
super();
@ -117,8 +118,9 @@ export class ClickLinkGesture extends Disposable {
this._editor = editor;
this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier));
this.lastMouseMoveEvent = null;
this.hasTriggerKeyOnMouseDown = false;
this._lastMouseMoveEvent = null;
this._hasTriggerKeyOnMouseDown = false;
this._lineNumberOnMouseDown = 0;
this._register(this._editor.onDidChangeConfiguration((e) => {
if (e.hasChanged(EditorOption.multiCursorModifier)) {
@ -127,77 +129,80 @@ export class ClickLinkGesture extends Disposable {
return;
}
this._opts = newOpts;
this.lastMouseMoveEvent = null;
this.hasTriggerKeyOnMouseDown = false;
this._lastMouseMoveEvent = null;
this._hasTriggerKeyOnMouseDown = false;
this._lineNumberOnMouseDown = 0;
this._onCancel.fire();
}
}));
this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this.onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts))));
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts))));
this._register(this._editor.onMouseDrag(() => this.resetHandler()));
this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts))));
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this._onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts))));
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this._onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts))));
this._register(this._editor.onMouseDrag(() => this._resetHandler()));
this._register(this._editor.onDidChangeCursorSelection((e) => this.onDidChangeCursorSelection(e)));
this._register(this._editor.onDidChangeModel((e) => this.resetHandler()));
this._register(this._editor.onDidChangeModelContent(() => this.resetHandler()));
this._register(this._editor.onDidChangeCursorSelection((e) => this._onDidChangeCursorSelection(e)));
this._register(this._editor.onDidChangeModel((e) => this._resetHandler()));
this._register(this._editor.onDidChangeModelContent(() => this._resetHandler()));
this._register(this._editor.onDidScrollChange((e) => {
if (e.scrollTopChanged || e.scrollLeftChanged) {
this.resetHandler();
this._resetHandler();
}
}));
}
private onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void {
private _onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void {
if (e.selection && e.selection.startColumn !== e.selection.endColumn) {
this.resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827)
this._resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827)
}
}
private onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void {
this.lastMouseMoveEvent = mouseEvent;
private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void {
this._lastMouseMoveEvent = mouseEvent;
this._onMouseMoveOrRelevantKeyDown.fire([mouseEvent, null]);
}
private onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void {
private _onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void {
// We need to record if we had the trigger key on mouse down because someone might select something in the editor
// holding the mouse down and then while mouse is down start to press Ctrl/Cmd to start a copy operation and then
// release the mouse button without wanting to do the navigation.
// With this flag we prevent goto definition if the mouse was down before the trigger key was pressed.
this.hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier;
this._hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier;
this._lineNumberOnMouseDown = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0;
}
private onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void {
if (this.hasTriggerKeyOnMouseDown) {
private _onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void {
const currentLineNumber = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0;
if (this._hasTriggerKeyOnMouseDown && this._lineNumberOnMouseDown && this._lineNumberOnMouseDown === currentLineNumber) {
this._onExecute.fire(mouseEvent);
}
}
private onEditorKeyDown(e: ClickLinkKeyboardEvent): void {
private _onEditorKeyDown(e: ClickLinkKeyboardEvent): void {
if (
this.lastMouseMoveEvent
this._lastMouseMoveEvent
&& (
e.keyCodeIsTriggerKey // User just pressed Ctrl/Cmd (normal goto definition)
|| (e.keyCodeIsSideBySideKey && e.hasTriggerModifier) // User pressed Ctrl/Cmd+Alt (goto definition to the side)
)
) {
this._onMouseMoveOrRelevantKeyDown.fire([this.lastMouseMoveEvent, e]);
this._onMouseMoveOrRelevantKeyDown.fire([this._lastMouseMoveEvent, e]);
} else if (e.hasTriggerModifier) {
this._onCancel.fire(); // remove decorations if user holds another key with ctrl/cmd to prevent accident goto declaration
}
}
private onEditorKeyUp(e: ClickLinkKeyboardEvent): void {
private _onEditorKeyUp(e: ClickLinkKeyboardEvent): void {
if (e.keyCodeIsTriggerKey) {
this._onCancel.fire();
}
}
private resetHandler(): void {
this.lastMouseMoveEvent = null;
this.hasTriggerKeyOnMouseDown = false;
private _resetHandler(): void {
this._lastMouseMoveEvent = null;
this._hasTriggerKeyOnMouseDown = false;
this._onCancel.fire();
}
}

View file

@ -0,0 +1,206 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IRange } from 'vs/editor/common/core/range';
import { withNullAsUndefined } from 'vs/base/common/types';
import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess';
import { IPosition } from 'vs/editor/common/core/position';
export const GOTO_LINE_PREFIX = ':';
interface IGotoLineQuickPickItem extends IQuickPickItem, Partial<IPosition> { }
export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider {
provide(picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Provide based on current active editor
let pickerDisposable = this.doProvide(picker, token);
disposables.add(toDisposable(() => pickerDisposable.dispose()));
// Re-create whenever the active editor changes
disposables.add(this.onDidActiveTextEditorControlChange(() => {
pickerDisposable.dispose();
pickerDisposable = this.doProvide(picker, token);
}));
return disposables;
}
private doProvide(picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
// With text control
if (this.activeTextEditorControl) {
return this.doProvideWithTextEditor(this.activeTextEditorControl, picker, token);
}
// Without text control
return this.doProvideWithoutTextEditor(picker);
}
private doProvideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoLine', "Open a text file first to go to a line.");
picker.items = [{ label }];
picker.ariaLabel = label;
return Disposable.None;
}
private doProvideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Restore any view state if this picker was closed
// without actually going to a line
const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState());
once(token.onCancellationRequested)(() => {
if (lastKnownEditorViewState) {
editor.restoreViewState(lastKnownEditorViewState);
}
});
// Goto line once picked
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
if (!this.isValidLineNumber(editor, item.lineNumber)) {
return;
}
this.gotoLine(editor, this.toRange(item.lineNumber, item.column), picker.keyMods);
picker.hide();
}
}));
// React to picker changes
const updatePickerAndEditor = () => {
const position = this.parsePosition(editor, picker.value.trim().substr(GOTO_LINE_PREFIX.length));
const label = this.getPickLabel(editor, position.lineNumber, position.column);
// Picker
picker.items = [{
lineNumber: position.lineNumber,
column: position.column,
label
}];
// ARIA Label
picker.ariaLabel = label;
// Clear decorations for invalid range
if (!this.isValidLineNumber(editor, position.lineNumber)) {
this.clearDecorations(editor);
return;
}
// Reveal
const range = this.toRange(position.lineNumber, position.column);
editor.revealRangeInCenter(range, ScrollType.Smooth);
// Decorate
this.addDecorations(editor, range);
};
updatePickerAndEditor();
disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
// Clean up decorations on dispose
disposables.add(toDisposable(() => this.clearDecorations(editor)));
return disposables;
}
private toRange(lineNumber = 1, column = 1): IRange {
return {
startLineNumber: lineNumber,
startColumn: column,
endLineNumber: lineNumber,
endColumn: column
};
}
private parsePosition(editor: IEditor, value: string): IPosition {
// Support line-col formats of `line,col`, `line:col`, `line#col`
const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part));
const endLine = this.lineCount(editor) + 1;
return {
lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0],
column: numbers[1]
};
}
private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined): string {
// Location valid: indicate this as picker label
if (this.isValidLineNumber(editor, lineNumber)) {
if (this.isValidColumn(editor, lineNumber, column)) {
return localize('gotoLineColumnLabel', "Go to line {0} and column {1}.", lineNumber, column);
}
return localize('gotoLineLabel', "Go to line {0}.", lineNumber);
}
// Location invalid: show generic label
const position = editor.getPosition() || { lineNumber: 1, column: 1 };
const lineCount = this.lineCount(editor);
if (lineCount > 1) {
return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Column: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
}
return localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
}
private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean {
if (!lineNumber || typeof lineNumber !== 'number') {
return false;
}
return lineNumber > 0 && lineNumber <= this.lineCount(editor);
}
private isValidColumn(editor: IEditor, lineNumber: number, column: number | undefined): boolean {
if (!column || typeof column !== 'number') {
return false;
}
const model = this.getModel(editor);
if (!model) {
return false;
}
const positionCandidate = { lineNumber, column };
return model.validatePosition(positionCandidate).equals(positionCandidate);
}
private lineCount(editor: IEditor): number {
return this.getModel(editor)?.getLineCount() ?? 0;
}
private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {
return isDiffEditor(editor) ?
editor.getModel()?.modified :
editor.getModel() as ITextModel;
}
protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
editor.setSelection(range);
editor.revealRangeInCenter(range, ScrollType.Smooth);
editor.focus();
}
}

View file

@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model';
import { IRange } from 'vs/editor/common/core/range';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry';
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
interface IEditorLineDecoration {
rangeHighlightId: string;
overviewRulerDecorationId: string;
}
/**
* A reusable quick access provider for the editor with support for adding decorations.
*/
export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider {
/**
* Subclasses to provide an event when the active editor control changes.
*/
abstract readonly onDidActiveTextEditorControlChange: Event<void>;
/**
* Subclasses to provide the current active editor control.
*/
abstract activeTextEditorControl: IEditor | undefined;
/**
* Subclasses to implement the quick access picker.
*/
abstract provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
//#region Decorations Utils
private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;
protected addDecorations(editor: IEditor, range: IRange): void {
editor.changeDecorations(changeAccessor => {
// Reset old decorations if any
const deleteDecorations: string[] = [];
if (this.rangeHighlightDecorationId) {
deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
this.rangeHighlightDecorationId = undefined;
}
// Add new decorations for the range
const newDecorations: IModelDeltaDecoration[] = [
// highlight the entire line on the range
{
range,
options: {
className: 'rangeHighlight',
isWholeLine: true
}
},
// also add overview ruler highlight
{
range,
options: {
overviewRuler: {
color: themeColorFromId(overviewRulerRangeHighlight),
position: OverviewRulerLane.Full
}
}
}
];
const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };
});
}
protected clearDecorations(editor: IEditor): void {
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
if (rangeHighlightDecorationId) {
editor.changeDecorations(changeAccessor => {
changeAccessor.deltaDecorations([
rangeHighlightDecorationId.overviewRulerDecorationId,
rangeHighlightDecorationId.rangeHighlightId
], []);
});
this.rangeHighlightDecorationId = undefined;
}
}
//#endregion
}

View file

@ -10,6 +10,8 @@ import 'vs/editor/standalone/browser/inspectTokens/inspectTokens';
import 'vs/editor/standalone/browser/quickOpen/gotoLine';
import 'vs/editor/standalone/browser/quickOpen/quickCommand';
import 'vs/editor/standalone/browser/quickOpen/quickOutline';
import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess';
import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess';
import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch';
import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast';

View file

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractGotoLineQuickAccessProvider, GOTO_LINE_PREFIX } from 'vs/editor/contrib/quickAccess/gotoLine';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { GoToLineNLS } from 'vs/editor/common/standaloneStrings';
import { Event } from 'vs/base/common/event';
export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider {
readonly onDidActiveTextEditorControlChange = Event.None;
constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) {
super();
}
get activeTextEditorControl() {
return withNullAsUndefined(this.editorService.getFocusedCodeEditor());
}
}
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
ctor: StandaloneGotoLineQuickAccessProvider,
prefix: GOTO_LINE_PREFIX,
helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }]
});

View file

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings';
import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess';
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).defaultProvider = {
ctor: HelpQuickAccessProvider,
prefix: '',
helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }]
};

View file

@ -10,10 +10,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IQuickInputService, IQuickInputButton, IQuickPickItem, IQuickPick, IInputBox, IQuickNavigateConfiguration, IPickOptions, QuickPickInput, IInputOptions } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
@ -21,6 +18,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput';
import { QuickInputService, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInput';
import { once } from 'vs/base/common/functional';
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
export class EditorScopedQuickInputServiceImpl extends QuickInputService {
@ -28,15 +26,13 @@ export class EditorScopedQuickInputServiceImpl extends QuickInputService {
constructor(
editor: ICodeEditor,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@ILayoutService layoutService: ILayoutService
) {
super({ args: Object.create(null) } as IEnvironmentService, configurationService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService);
super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService);
// Use the passed in code editor as host for the quick input widget
const contribution = QuickInputEditorContribution.get(editor);
@ -81,6 +77,8 @@ export class StandaloneQuickInputServiceImpl implements IQuickInputService {
return quickInputService;
}
get quickAccess(): IQuickAccessController { return this.activeService.quickAccess; }
get backButton(): IQuickInputButton { return this.activeService.backButton; }
get onShow() { return this.activeService.onShow; }

View file

@ -397,6 +397,25 @@ suite('Editor Controller - Cursor', () => {
assertCursor(thisCursor, new Position(1, LINE1.length + 1));
});
test('issue #44465: cursor position not correct when move', () => {
thisCursor.setSelections('test', [new Selection(1, 5, 1, 5)]);
// going once up on the first line remembers the offset visual columns
moveUp(thisCursor);
assertCursor(thisCursor, new Position(1, 1));
moveDown(thisCursor);
assertCursor(thisCursor, new Position(2, 2));
moveUp(thisCursor);
assertCursor(thisCursor, new Position(1, 5));
// going twice up on the first line discards the offset visual columns
moveUp(thisCursor);
assertCursor(thisCursor, new Position(1, 1));
moveUp(thisCursor);
assertCursor(thisCursor, new Position(1, 1));
moveDown(thisCursor);
assertCursor(thisCursor, new Position(2, 1));
});
// --------- move to beginning of line
test('move to beginning of line', () => {
@ -5044,6 +5063,28 @@ suite('autoClosingPairs', () => {
mode.dispose();
});
test('issue #90016: allow accents on mac US intl keyboard to surround selection', () => {
let mode = new AutoClosingMode();
usingCursor({
text: [
'test'
],
languageIdentifier: mode.getLanguageIdentifier()
}, (model, cursor) => {
cursor.setSelections('test', [new Selection(1, 1, 1, 5)]);
// Typing ` + e on the mac US intl kb layout
cursorCommand(cursor, H.CompositionStart, null, 'keyboard');
cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard');
cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard');
cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard');
cursorCommand(cursor, H.CompositionEnd, null, 'keyboard');
assert.equal(model.getValue(), '\'test\'');
});
mode.dispose();
});
test('issue #53357: Over typing ignores characters after backslash', () => {
let mode = new AutoClosingMode();
usingCursor({

View file

@ -373,7 +373,7 @@ export interface IAction2Options extends ICommandAction {
/**
* One or many menu items.
*/
menu?: OneOrN<{ id: MenuId } & Omit<IMenuItem, 'command'>>;
menu?: OneOrN<{ id: MenuId } & Omit<IMenuItem, 'command'> & { command?: Partial<Omit<ICommandAction, 'id'>> }>;
/**
* One keybinding.
@ -396,39 +396,43 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
const disposables = new DisposableStore();
const action = new ctor();
const { f1, menu: menus, keybinding, description, ...command } = action.desc;
// command
disposables.add(CommandsRegistry.registerCommand({
id: action.desc.id,
id: command.id,
handler: (accessor, ...args) => action.run(accessor, ...args),
description: action.desc.description,
description: description,
}));
// menu
if (Array.isArray(action.desc.menu)) {
for (let item of action.desc.menu) {
disposables.add(MenuRegistry.appendMenuItem(item.id, { command: action.desc, ...item }));
if (Array.isArray(menus)) {
for (let item of menus) {
const { command: commandOverrides, ...menu } = item;
disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command, ...commandOverrides }, ...menu }));
}
} else if (action.desc.menu) {
disposables.add(MenuRegistry.appendMenuItem(action.desc.menu.id, { command: action.desc, ...action.desc.menu }));
} else if (menus) {
const { command: commandOverrides, ...menu } = menus;
disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, ...commandOverrides }, ...menu }));
}
if (action.desc.f1) {
disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: action.desc, ...action.desc }));
if (f1) {
disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command }));
}
// keybinding
if (Array.isArray(action.desc.keybinding)) {
for (let item of action.desc.keybinding) {
if (Array.isArray(keybinding)) {
for (let item of keybinding) {
KeybindingsRegistry.registerKeybindingRule({
...item,
id: action.desc.id,
when: ContextKeyExpr.and(action.desc.precondition, item.when)
id: command.id,
when: ContextKeyExpr.and(command.precondition, item.when)
});
}
} else if (action.desc.keybinding) {
} else if (keybinding) {
KeybindingsRegistry.registerKeybindingRule({
...action.desc.keybinding,
id: action.desc.id,
when: ContextKeyExpr.and(action.desc.precondition, action.desc.keybinding.when)
...keybinding,
id: command.id,
when: ContextKeyExpr.and(command.precondition, keybinding.when)
});
}

View file

@ -142,6 +142,10 @@ class ConfigAwareContextValuesContainer extends Context {
case 'string':
value = configValue;
break;
default:
if (Array.isArray(configValue)) {
value = JSON.stringify(configValue);
}
}
this._values.set(key, value);

View file

@ -38,7 +38,7 @@ export interface IGrammar {
}
export interface IJSONValidation {
fileMatch: string;
fileMatch: string | string[];
url: string;
}

View file

@ -49,6 +49,7 @@ export interface IssueReporterExtensionData {
version: string;
id: string;
isTheme: boolean;
isBuiltin: boolean;
displayName: string | undefined;
repositoryUrl: string | undefined;
bugsUrl: string | undefined;

View file

@ -1153,7 +1153,7 @@ configurationRegistry.registerConfiguration({
[horizontalScrollingKey]: {
'type': 'boolean',
'default': false,
'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench.")
'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")
},
'workbench.tree.horizontalScrolling': {
'type': 'boolean',

View file

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
interface IHelpQuickAccessPickItem extends IQuickPickItem {
prefix: string;
}
export class HelpQuickAccessProvider implements IQuickAccessProvider {
private readonly registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { }
provide(picker: IQuickPick<IHelpQuickAccessPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Open a picker with the selected value if picked
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
this.quickInputService.quickAccess.show(item.prefix);
}
}));
// Fill in all providers separated by editor/global scope
const { editorProviders, globalProviders } = this.getQuickAccessProviders();
picker.items = editorProviders.length === 0 || globalProviders.length === 0 ?
// Without groups
[
...(editorProviders.length === 0 ? globalProviders : editorProviders)
] :
// With groups
[
{ label: localize('globalCommands', "global commands"), type: 'separator' },
...globalProviders,
{ label: localize('editorCommands', "editor commands"), type: 'separator' },
...editorProviders
];
return disposables;
}
private getQuickAccessProviders(): { editorProviders: IHelpQuickAccessPickItem[], globalProviders: IHelpQuickAccessPickItem[] } {
const globalProviders: IHelpQuickAccessPickItem[] = [];
const editorProviders: IHelpQuickAccessPickItem[] = [];
for (const provider of this.registry.getQuickAccessProviders().sort((p1, p2) => p1.prefix.localeCompare(p2.prefix))) {
for (const helpEntry of provider.helpEntries) {
const prefix = helpEntry.prefix || provider.prefix;
const label = prefix || '\u2026' /* ... */;
(helpEntry.needsEditor ? editorProviders : globalProviders).push({
prefix,
label,
description: helpEntry.description,
ariaLabel: localize('entryAriaLabel', "{0}, picker help", label)
});
}
}
return { editorProviders, globalProviders };
}
}

View file

@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor } from 'vs/platform/quickinput/common/quickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { once } from 'vs/base/common/functional';
export class QuickAccessController extends Disposable implements IQuickAccessController {
private readonly registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
private readonly mapProviderToDescriptor = new Map<IQuickAccessProviderDescriptor, IQuickAccessProvider>();
private lastActivePicker: IQuickPick<IQuickPickItem> | undefined = undefined;
constructor(
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
}
show(value = ''): void {
const disposables = new DisposableStore();
// Hide any previous picker if any
this.lastActivePicker?.hide();
// Find provider for the value to show
const [provider, descriptor] = this.getOrInstantiateProvider(value);
// Create a picker for the provider to use with the initial value
// and adjust the filtering to exclude the prefix from filtering
const picker = disposables.add(this.quickInputService.createQuickPick());
picker.placeholder = descriptor.placeholder;
picker.value = value;
picker.valueSelection = [value.length, value.length];
picker.contextKey = descriptor.contextKey;
picker.filterValue = (value: string) => value.substring(descriptor.prefix.length);
// Remember as last active picker and clean up once picker get's disposed
this.lastActivePicker = picker;
disposables.add(toDisposable(() => {
if (picker === this.lastActivePicker) {
this.lastActivePicker = undefined;
}
}));
// Create a cancellation token source that is valid as long as the
// picker has not been closed without picking an item
const cts = disposables.add(new CancellationTokenSource());
once(picker.onDidHide)(() => {
if (picker.selectedItems.length === 0) {
cts.cancel();
}
// Start to dispose once picker hides
disposables.dispose();
});
// Whenever the value changes, check if the provider has
// changed and if so - re-create the picker from the beginning
disposables.add(picker.onDidChangeValue(value => {
const [providerForValue] = this.getOrInstantiateProvider(value);
if (providerForValue !== provider) {
this.show(value);
}
}));
// Ask provider to fill the picker as needed
disposables.add(provider.provide(picker, cts.token));
// Finally, show the picker. This is important because a provider
// may not call this and then our disposables would leak that rely
// on the onDidHide event.
picker.show();
}
private getOrInstantiateProvider(value: string): [IQuickAccessProvider, IQuickAccessProviderDescriptor] {
const providerDescriptor = this.registry.getQuickAccessProvider(value) || this.registry.defaultProvider;
let provider = this.mapProviderToDescriptor.get(providerDescriptor);
if (!provider) {
provider = this.instantiationService.createInstance(providerDescriptor.ctor);
this.mapProviderToDescriptor.set(providerDescriptor, provider);
}
return [provider, providerDescriptor];
}
}

View file

@ -9,16 +9,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, listFocusBackground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground } from 'vs/platform/theme/common/colorRegistry';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { computeStyles } from 'vs/platform/theme/common/styler';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { QuickInputController, IQuickInputStyles } from 'vs/base/parts/quickinput/browser/quickInput';
import { QuickInputController, IQuickInputStyles, IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { List, IListOptions } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
import { QuickAccessController } from 'vs/platform/quickinput/browser/quickAccess';
export interface IQuickInputControllerHost extends ILayoutService { }
@ -40,28 +39,34 @@ export class QuickInputService extends Themable implements IQuickInputService {
return this._controller;
}
private _quickAccess: IQuickAccessController | undefined;
get quickAccess(): IQuickAccessController {
if (!this._quickAccess) {
this._quickAccess = this._register(this.instantiationService.createInstance(QuickAccessController));
}
return this._quickAccess;
}
private readonly contexts = new Map<string, IContextKey<boolean>>();
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@ILayoutService private readonly layoutService: ILayoutService
@ILayoutService protected readonly layoutService: ILayoutService
) {
super(themeService);
}
protected createController(host: IQuickInputControllerHost = this.layoutService): QuickInputController {
const controller = this._register(new QuickInputController({
protected createController(host: IQuickInputControllerHost = this.layoutService, options?: Partial<IQuickInputOptions>): QuickInputController {
const defaultOptions: IQuickInputOptions = {
idPrefix: 'quickInput_', // Constant since there is still only one.
container: host.container,
ignoreFocusOut: () => this.environmentService.args['sticky-quickopen'] || !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'),
ignoreFocusOut: () => false,
isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(),
backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined,
backKeybindingLabel: () => undefined,
setContextKey: (id?: string) => this.setContextKey(id),
returnFocus: () => host.focus(),
createList: <T>(
@ -72,6 +77,11 @@ export class QuickInputService extends Themable implements IQuickInputService {
options: IListOptions<T>,
) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List<T>,
styles: this.computeStyles()
};
const controller = this._register(new QuickInputController({
...defaultOptions,
...options
}));
controller.layout(host.dimension, host.offset?.top ?? 0);

View file

@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Registry } from 'vs/platform/registry/common/platform';
import { first } from 'vs/base/common/arrays';
import { startsWith } from 'vs/base/common/strings';
import { assertIsDefined } from 'vs/base/common/types';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IQuickAccessController {
/**
* Open the quick access picker with the optional value prefilled.
*/
show(value?: string): void;
}
export interface IQuickAccessProvider {
/**
* Called whenever a prefix was typed into quick pick that matches the provider.
*
* @param picker the picker to use for showing provider results. The picker is
* automatically shown after the method returns, no need to call `show()`.
* @param token providers have to check the cancellation token everytime after
* a long running operation or from event handlers because it could be that the
* picker has been closed or changed meanwhile. The token can be used to find out
* that the picker was closed without picking an entry (e.g. was canceled by the user).
* @return a disposable that will automatically be disposed when the picker
* closes or is replaced by another picker.
*/
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
}
export interface IQuickAccessProviderHelp {
/**
* The prefix to show for the help entry. If not provided,
* the prefix used for registration will be taken.
*/
prefix?: string;
/**
* A description text to help understand the intent of the provider.
*/
description: string;
/**
* Separation between provider for editors and global ones.
*/
needsEditor: boolean;
}
export interface IQuickAccessProviderDescriptor {
/**
* The actual provider that will be instantiated as needed.
*/
readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): IQuickAccessProvider };
/**
* The prefix for quick access picker to use the provider for.
*/
readonly prefix: string;
/**
* A placeholder to use for the input field when the provider is active.
* This will also be read out by screen readers and thus helps for
* accessibility.
*/
readonly placeholder?: string;
/**
* Documentation for the provider in the quick access help.
*/
readonly helpEntries: IQuickAccessProviderHelp[];
/**
* A context key that will be set automatically when the
* picker for the provider is showing.
*/
readonly contextKey?: string;
}
export const Extensions = {
Quickaccess: 'workbench.contributions.quickaccess'
};
export interface IQuickAccessRegistry {
/**
* The default provider to use when no other provider matches.
*/
defaultProvider: IQuickAccessProviderDescriptor;
/**
* Registers a quick access provider to the platform.
*/
registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable;
/**
* Get all registered quick access providers.
*/
getQuickAccessProviders(): IQuickAccessProviderDescriptor[];
/**
* Get a specific quick access provider for a given prefix.
*/
getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined;
}
class QuickAccessRegistry implements IQuickAccessRegistry {
private providers: IQuickAccessProviderDescriptor[] = [];
private _defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined;
get defaultProvider(): IQuickAccessProviderDescriptor { return assertIsDefined(this._defaultProvider); }
set defaultProvider(provider: IQuickAccessProviderDescriptor) { this._defaultProvider = provider; }
registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable {
this.providers.push(provider);
// sort the providers by decreasing prefix length, such that longer
// prefixes take priority: 'ext' vs 'ext install' - the latter should win
this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length);
return toDisposable(() => this.providers.splice(this.providers.indexOf(provider), 1));
}
getQuickAccessProviders(): IQuickAccessProviderDescriptor[] {
return [this.defaultProvider, ...this.providers];
}
getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined {
return prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined;
}
}
Registry.add(Extensions.Quickaccess, new QuickAccessRegistry());

View file

@ -7,8 +7,9 @@ import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput';
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
export { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput';
export * from 'vs/base/parts/quickinput/common/quickInput';
export const IQuickInputService = createDecorator<IQuickInputService>('quickInputService');
@ -18,20 +19,29 @@ export interface IQuickInputService {
_serviceBrand: undefined;
/**
* Provides access to the back button in quick input.
*/
readonly backButton: IQuickInputButton;
/**
* Allows to register on the event that quick input is showing
* Provides access to the quick access providers.
*/
readonly quickAccess: IQuickAccessController;
/**
* Allows to register on the event that quick input is showing.
*/
readonly onShow: Event<void>;
/**
* Allows to register on the event that quick input is hiding
* Allows to register on the event that quick input is hiding.
*/
readonly onHide: Event<void>;
/**
* Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any.
* Opens the quick input box for selecting items and returns a promise
* with the user selected item(s) if any.
*/
pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>;
pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>;
@ -42,20 +52,46 @@ export interface IQuickInputService {
*/
input(options?: IInputOptions, token?: CancellationToken): Promise<string>;
/**
* Provides raw access to the quick pick controller.
*/
createQuickPick<T extends IQuickPickItem>(): IQuickPick<T>;
/**
* Provides raw access to the quick input controller.
*/
createInputBox(): IInputBox;
/**
* Moves focus into quick input.
*/
focus(): void;
/**
* Toggle the checked state of the selected item.
*/
toggle(): void;
/**
* Navigate inside the opened quick input list.
*/
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void;
accept(): Promise<void>;
/**
* Navigate back in a multi-step quick input.
*/
back(): Promise<void>;
/**
* Accept the selected item.
*/
accept(): Promise<void>;
/**
* Cancels quick input and closes it.
*/
cancel(): Promise<void>;
// TODO@Ben remove once quick open is gone
hide(focusLost?: boolean): void;
}

View file

@ -0,0 +1,543 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'vs/platform/registry/common/platform';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
import { localize } from 'vs/nls';
// ------ API types
// color registry
export const Extensions = {
IconContribution: 'base.contributions.icons'
};
export interface IconDefaults {
font?: string;
character: string;
}
export interface IconContribution {
id: string;
description: string;
deprecationMessage?: string;
defaults: IconDefaults;
}
export interface IIconRegistry {
readonly onDidChangeSchema: Event<void>;
/**
* Register a icon to the registry.
* @param id The icon id
* @param defaults The default values
* @description the description
*/
registerIcon(id: string, defaults: IconDefaults, description: string): ThemeIcon;
/**
* Register a icon to the registry.
*/
deregisterIcon(id: string): void;
/**
* Get all icon contributions
*/
getIcons(): IconContribution[];
/**
* JSON schema for an object to assign icon values to one of the color contributions.
*/
getIconSchema(): IJSONSchema;
/**
* JSON schema to for a reference to a icon contribution.
*/
getIconReferenceSchema(): IJSONSchema;
}
class IconRegistry implements IIconRegistry {
private readonly _onDidChangeSchema = new Emitter<void>();
readonly onDidChangeSchema: Event<void> = this._onDidChangeSchema.event;
private iconsById: { [key: string]: IconContribution };
private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} };
private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] };
constructor() {
this.iconsById = {};
}
public registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon {
let iconContribution: IconContribution = { id, description, defaults, deprecationMessage };
this.iconsById[id] = iconContribution;
let propertySchema: IJSONSchema = { type: 'object', description, properties: { font: { type: 'string' }, fontCharacter: { type: 'string' } }, defaultSnippets: [{ body: { fontCharacter: '\\\\e030' } }] };
if (deprecationMessage) {
propertySchema.deprecationMessage = deprecationMessage;
}
this.iconSchema.properties[id] = propertySchema;
this.iconReferenceSchema.enum.push(id);
this.iconReferenceSchema.enumDescriptions.push(description);
this._onDidChangeSchema.fire();
return { id };
}
public deregisterIcon(id: string): void {
delete this.iconsById[id];
delete this.iconSchema.properties[id];
const index = this.iconReferenceSchema.enum.indexOf(id);
if (index !== -1) {
this.iconReferenceSchema.enum.splice(index, 1);
this.iconReferenceSchema.enumDescriptions.splice(index, 1);
}
this._onDidChangeSchema.fire();
}
public getIcons(): IconContribution[] {
return Object.keys(this.iconsById).map(id => this.iconsById[id]);
}
public getIconSchema(): IJSONSchema {
return this.iconSchema;
}
public getIconReferenceSchema(): IJSONSchema {
return this.iconReferenceSchema;
}
public toString() {
let sorter = (a: string, b: string) => {
let cat1 = a.indexOf('.') === -1 ? 0 : 1;
let cat2 = b.indexOf('.') === -1 ? 0 : 1;
if (cat1 !== cat2) {
return cat1 - cat2;
}
return a.localeCompare(b);
};
return Object.keys(this.iconsById).sort(sorter).map(k => `- \`${k}\`: ${this.iconsById[k].description}`).join('\n');
}
}
const iconRegistry = new IconRegistry();
platform.Registry.add(Extensions.IconContribution, iconRegistry);
export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon {
return iconRegistry.registerIcon(id, defaults, description, deprecationMessage);
}
export function getIconRegistry(): IIconRegistry {
return iconRegistry;
}
registerIcon('add', { character: '\ea60' }, localize('add', ''));
registerIcon('plus', { character: '\ea60' }, localize('plus', ''));
registerIcon('gist-new', { character: '\ea60' }, localize('gist-new', ''));
registerIcon('repo-create', { character: '\ea60' }, localize('repo-create', ''));
registerIcon('lightbulb', { character: '\ea61' }, localize('lightbulb', ''));
registerIcon('light-bulb', { character: '\ea61' }, localize('light-bulb', ''));
registerIcon('repo', { character: '\ea62' }, localize('repo', ''));
registerIcon('repo-delete', { character: '\ea62' }, localize('repo-delete', ''));
registerIcon('gist-fork', { character: '\ea63' }, localize('gist-fork', ''));
registerIcon('repo-forked', { character: '\ea63' }, localize('repo-forked', ''));
registerIcon('git-pull-request', { character: '\ea64' }, localize('git-pull-request', ''));
registerIcon('git-pull-request-abandoned', { character: '\ea64' }, localize('git-pull-request-abandoned', ''));
registerIcon('record-keys', { character: '\ea65' }, localize('record-keys', ''));
registerIcon('keyboard', { character: '\ea65' }, localize('keyboard', ''));
registerIcon('tag', { character: '\ea66' }, localize('tag', ''));
registerIcon('tag-add', { character: '\ea66' }, localize('tag-add', ''));
registerIcon('tag-remove', { character: '\ea66' }, localize('tag-remove', ''));
registerIcon('person', { character: '\ea67' }, localize('person', ''));
registerIcon('person-add', { character: '\ea67' }, localize('person-add', ''));
registerIcon('person-follow', { character: '\ea67' }, localize('person-follow', ''));
registerIcon('person-outline', { character: '\ea67' }, localize('person-outline', ''));
registerIcon('person-filled', { character: '\ea67' }, localize('person-filled', ''));
registerIcon('git-branch', { character: '\ea68' }, localize('git-branch', ''));
registerIcon('git-branch-create', { character: '\ea68' }, localize('git-branch-create', ''));
registerIcon('git-branch-delete', { character: '\ea68' }, localize('git-branch-delete', ''));
registerIcon('source-control', { character: '\ea68' }, localize('source-control', ''));
registerIcon('mirror', { character: '\ea69' }, localize('mirror', ''));
registerIcon('mirror-public', { character: '\ea69' }, localize('mirror-public', ''));
registerIcon('star', { character: '\ea6a' }, localize('star', ''));
registerIcon('star-add', { character: '\ea6a' }, localize('star-add', ''));
registerIcon('star-delete', { character: '\ea6a' }, localize('star-delete', ''));
registerIcon('star-empty', { character: '\ea6a' }, localize('star-empty', ''));
registerIcon('comment', { character: '\ea6b' }, localize('comment', ''));
registerIcon('comment-add', { character: '\ea6b' }, localize('comment-add', ''));
registerIcon('alert', { character: '\ea6c' }, localize('alert', ''));
registerIcon('warning', { character: '\ea6c' }, localize('warning', ''));
registerIcon('search', { character: '\ea6d' }, localize('search', ''));
registerIcon('search-save', { character: '\ea6d' }, localize('search-save', ''));
registerIcon('log-out', { character: '\ea6e' }, localize('log-out', ''));
registerIcon('sign-out', { character: '\ea6e' }, localize('sign-out', ''));
registerIcon('log-in', { character: '\ea6f' }, localize('log-in', ''));
registerIcon('sign-in', { character: '\ea6f' }, localize('sign-in', ''));
registerIcon('eye', { character: '\ea70' }, localize('eye', ''));
registerIcon('eye-unwatch', { character: '\ea70' }, localize('eye-unwatch', ''));
registerIcon('eye-watch', { character: '\ea70' }, localize('eye-watch', ''));
registerIcon('circle-filled', { character: '\ea71' }, localize('circle-filled', ''));
registerIcon('primitive-dot', { character: '\ea71' }, localize('primitive-dot', ''));
registerIcon('close-dirty', { character: '\ea71' }, localize('close-dirty', ''));
registerIcon('debug-breakpoint', { character: '\ea71' }, localize('debug-breakpoint', ''));
registerIcon('debug-breakpoint-disabled', { character: '\ea71' }, localize('debug-breakpoint-disabled', ''));
registerIcon('debug-hint', { character: '\ea71' }, localize('debug-hint', ''));
registerIcon('primitive-square', { character: '\ea72' }, localize('primitive-square', ''));
registerIcon('edit', { character: '\ea73' }, localize('edit', ''));
registerIcon('pencil', { character: '\ea73' }, localize('pencil', ''));
registerIcon('info', { character: '\ea74' }, localize('info', ''));
registerIcon('issue-opened', { character: '\ea74' }, localize('issue-opened', ''));
registerIcon('gist-private', { character: '\ea75' }, localize('gist-private', ''));
registerIcon('git-fork-private', { character: '\ea75' }, localize('git-fork-private', ''));
registerIcon('lock', { character: '\ea75' }, localize('lock', ''));
registerIcon('mirror-private', { character: '\ea75' }, localize('mirror-private', ''));
registerIcon('close', { character: '\ea76' }, localize('close', ''));
registerIcon('remove-close', { character: '\ea76' }, localize('remove-close', ''));
registerIcon('x', { character: '\ea76' }, localize('x', ''));
registerIcon('repo-sync', { character: '\ea77' }, localize('repo-sync', ''));
registerIcon('sync', { character: '\ea77' }, localize('sync', ''));
registerIcon('clone', { character: '\ea78' }, localize('clone', ''));
registerIcon('desktop-download', { character: '\ea78' }, localize('desktop-download', ''));
registerIcon('beaker', { character: '\ea79' }, localize('beaker', ''));
registerIcon('microscope', { character: '\ea79' }, localize('microscope', ''));
registerIcon('vm', { character: '\ea7a' }, localize('vm', ''));
registerIcon('device-desktop', { character: '\ea7a' }, localize('device-desktop', ''));
registerIcon('file', { character: '\ea7b' }, localize('file', ''));
registerIcon('file-text', { character: '\ea7b' }, localize('file-text', ''));
registerIcon('more', { character: '\ea7c' }, localize('more', ''));
registerIcon('ellipsis', { character: '\ea7c' }, localize('ellipsis', ''));
registerIcon('kebab-horizontal', { character: '\ea7c' }, localize('kebab-horizontal', ''));
registerIcon('mail-reply', { character: '\ea7d' }, localize('mail-reply', ''));
registerIcon('reply', { character: '\ea7d' }, localize('reply', ''));
registerIcon('organization', { character: '\ea7e' }, localize('organization', ''));
registerIcon('organization-filled', { character: '\ea7e' }, localize('organization-filled', ''));
registerIcon('organization-outline', { character: '\ea7e' }, localize('organization-outline', ''));
registerIcon('new-file', { character: '\ea7f' }, localize('new-file', ''));
registerIcon('file-add', { character: '\ea7f' }, localize('file-add', ''));
registerIcon('new-folder', { character: '\ea80' }, localize('new-folder', ''));
registerIcon('file-directory-create', { character: '\ea80' }, localize('file-directory-create', ''));
registerIcon('trash', { character: '\ea81' }, localize('trash', ''));
registerIcon('trashcan', { character: '\ea81' }, localize('trashcan', ''));
registerIcon('history', { character: '\ea82' }, localize('history', ''));
registerIcon('clock', { character: '\ea82' }, localize('clock', ''));
registerIcon('folder', { character: '\ea83' }, localize('folder', ''));
registerIcon('file-directory', { character: '\ea83' }, localize('file-directory', ''));
registerIcon('symbol-folder', { character: '\ea83' }, localize('symbol-folder', ''));
registerIcon('logo-github', { character: '\ea84' }, localize('logo-github', ''));
registerIcon('mark-github', { character: '\ea84' }, localize('mark-github', ''));
registerIcon('github', { character: '\ea84' }, localize('github', ''));
registerIcon('terminal', { character: '\ea85' }, localize('terminal', ''));
registerIcon('console', { character: '\ea85' }, localize('console', ''));
registerIcon('repl', { character: '\ea85' }, localize('repl', ''));
registerIcon('zap', { character: '\ea86' }, localize('zap', ''));
registerIcon('symbol-event', { character: '\ea86' }, localize('symbol-event', ''));
registerIcon('error', { character: '\ea87' }, localize('error', ''));
registerIcon('stop', { character: '\ea87' }, localize('stop', ''));
registerIcon('variable', { character: '\ea88' }, localize('variable', ''));
registerIcon('symbol-variable', { character: '\ea88' }, localize('symbol-variable', ''));
registerIcon('array', { character: '\ea8a' }, localize('array', ''));
registerIcon('symbol-array', { character: '\ea8a' }, localize('symbol-array', ''));
registerIcon('symbol-module', { character: '\ea8b' }, localize('symbol-module', ''));
registerIcon('symbol-package', { character: '\ea8b' }, localize('symbol-package', ''));
registerIcon('symbol-namespace', { character: '\ea8b' }, localize('symbol-namespace', ''));
registerIcon('symbol-object', { character: '\ea8b' }, localize('symbol-object', ''));
registerIcon('symbol-method', { character: '\ea8c' }, localize('symbol-method', ''));
registerIcon('symbol-function', { character: '\ea8c' }, localize('symbol-function', ''));
registerIcon('symbol-constructor', { character: '\ea8c' }, localize('symbol-constructor', ''));
registerIcon('symbol-boolean', { character: '\ea8f' }, localize('symbol-boolean', ''));
registerIcon('symbol-null', { character: '\ea8f' }, localize('symbol-null', ''));
registerIcon('symbol-numeric', { character: '\ea90' }, localize('symbol-numeric', ''));
registerIcon('symbol-number', { character: '\ea90' }, localize('symbol-number', ''));
registerIcon('symbol-structure', { character: '\ea91' }, localize('symbol-structure', ''));
registerIcon('symbol-struct', { character: '\ea91' }, localize('symbol-struct', ''));
registerIcon('symbol-parameter', { character: '\ea92' }, localize('symbol-parameter', ''));
registerIcon('symbol-type-parameter', { character: '\ea92' }, localize('symbol-type-parameter', ''));
registerIcon('symbol-key', { character: '\ea93' }, localize('symbol-key', ''));
registerIcon('symbol-text', { character: '\ea93' }, localize('symbol-text', ''));
registerIcon('symbol-reference', { character: '\ea94' }, localize('symbol-reference', ''));
registerIcon('go-to-file', { character: '\ea94' }, localize('go-to-file', ''));
registerIcon('symbol-enum', { character: '\ea95' }, localize('symbol-enum', ''));
registerIcon('symbol-value', { character: '\ea95' }, localize('symbol-value', ''));
registerIcon('symbol-ruler', { character: '\ea96' }, localize('symbol-ruler', ''));
registerIcon('symbol-unit', { character: '\ea96' }, localize('symbol-unit', ''));
registerIcon('activate-breakpoints', { character: '\ea97' }, localize('activate-breakpoints', ''));
registerIcon('archive', { character: '\ea98' }, localize('archive', ''));
registerIcon('arrow-both', { character: '\ea99' }, localize('arrow-both', ''));
registerIcon('arrow-down', { character: '\ea9a' }, localize('arrow-down', ''));
registerIcon('arrow-left', { character: '\ea9b' }, localize('arrow-left', ''));
registerIcon('arrow-right', { character: '\ea9c' }, localize('arrow-right', ''));
registerIcon('arrow-small-down', { character: '\ea9d' }, localize('arrow-small-down', ''));
registerIcon('arrow-small-left', { character: '\ea9e' }, localize('arrow-small-left', ''));
registerIcon('arrow-small-right', { character: '\ea9f' }, localize('arrow-small-right', ''));
registerIcon('arrow-small-up', { character: '\eaa0' }, localize('arrow-small-up', ''));
registerIcon('arrow-up', { character: '\eaa1' }, localize('arrow-up', ''));
registerIcon('bell', { character: '\eaa2' }, localize('bell', ''));
registerIcon('bold', { character: '\eaa3' }, localize('bold', ''));
registerIcon('book', { character: '\eaa4' }, localize('book', ''));
registerIcon('bookmark', { character: '\eaa5' }, localize('bookmark', ''));
registerIcon('debug-breakpoint-conditional-unverified', { character: '\eaa6' }, localize('debug-breakpoint-conditional-unverified', ''));
registerIcon('debug-breakpoint-conditional', { character: '\eaa7' }, localize('debug-breakpoint-conditional', ''));
registerIcon('debug-breakpoint-conditional-disabled', { character: '\eaa7' }, localize('debug-breakpoint-conditional-disabled', ''));
registerIcon('debug-breakpoint-data-unverified', { character: '\eaa8' }, localize('debug-breakpoint-data-unverified', ''));
registerIcon('debug-breakpoint-data', { character: '\eaa9' }, localize('debug-breakpoint-data', ''));
registerIcon('debug-breakpoint-data-disabled', { character: '\eaa9' }, localize('debug-breakpoint-data-disabled', ''));
registerIcon('debug-breakpoint-log-unverified', { character: '\eaaa' }, localize('debug-breakpoint-log-unverified', ''));
registerIcon('debug-breakpoint-log', { character: '\eaab' }, localize('debug-breakpoint-log', ''));
registerIcon('debug-breakpoint-log-disabled', { character: '\eaab' }, localize('debug-breakpoint-log-disabled', ''));
registerIcon('briefcase', { character: '\eaac' }, localize('briefcase', ''));
registerIcon('broadcast', { character: '\eaad' }, localize('broadcast', ''));
registerIcon('browser', { character: '\eaae' }, localize('browser', ''));
registerIcon('bug', { character: '\eaaf' }, localize('bug', ''));
registerIcon('calendar', { character: '\eab0' }, localize('calendar', ''));
registerIcon('case-sensitive', { character: '\eab1' }, localize('case-sensitive', ''));
registerIcon('check', { character: '\eab2' }, localize('check', ''));
registerIcon('checklist', { character: '\eab3' }, localize('checklist', ''));
registerIcon('chevron-down', { character: '\eab4' }, localize('chevron-down', ''));
registerIcon('chevron-left', { character: '\eab5' }, localize('chevron-left', ''));
registerIcon('chevron-right', { character: '\eab6' }, localize('chevron-right', ''));
registerIcon('chevron-up', { character: '\eab7' }, localize('chevron-up', ''));
registerIcon('chrome-close', { character: '\eab8' }, localize('chrome-close', ''));
registerIcon('chrome-maximize', { character: '\eab9' }, localize('chrome-maximize', ''));
registerIcon('chrome-minimize', { character: '\eaba' }, localize('chrome-minimize', ''));
registerIcon('chrome-restore', { character: '\eabb' }, localize('chrome-restore', ''));
registerIcon('circle-outline', { character: '\eabc' }, localize('circle-outline', ''));
registerIcon('debug-breakpoint-unverified', { character: '\eabc' }, localize('debug-breakpoint-unverified', ''));
registerIcon('circle-slash', { character: '\eabd' }, localize('circle-slash', ''));
registerIcon('circuit-board', { character: '\eabe' }, localize('circuit-board', ''));
registerIcon('clear-all', { character: '\eabf' }, localize('clear-all', ''));
registerIcon('clippy', { character: '\eac0' }, localize('clippy', ''));
registerIcon('close-all', { character: '\eac1' }, localize('close-all', ''));
registerIcon('cloud-download', { character: '\eac2' }, localize('cloud-download', ''));
registerIcon('cloud-upload', { character: '\eac3' }, localize('cloud-upload', ''));
registerIcon('code', { character: '\eac4' }, localize('code', ''));
registerIcon('collapse-all', { character: '\eac5' }, localize('collapse-all', ''));
registerIcon('color-mode', { character: '\eac6' }, localize('color-mode', ''));
registerIcon('comment-discussion', { character: '\eac7' }, localize('comment-discussion', ''));
registerIcon('compare-changes', { character: '\eac8' }, localize('compare-changes', ''));
registerIcon('credit-card', { character: '\eac9' }, localize('credit-card', ''));
registerIcon('dash', { character: '\eacc' }, localize('dash', ''));
registerIcon('dashboard', { character: '\eacd' }, localize('dashboard', ''));
registerIcon('database', { character: '\eace' }, localize('database', ''));
registerIcon('debug-continue', { character: '\eacf' }, localize('debug-continue', ''));
registerIcon('debug-disconnect', { character: '\ead0' }, localize('debug-disconnect', ''));
registerIcon('debug-pause', { character: '\ead1' }, localize('debug-pause', ''));
registerIcon('debug-restart', { character: '\ead2' }, localize('debug-restart', ''));
registerIcon('debug-start', { character: '\ead3' }, localize('debug-start', ''));
registerIcon('debug-step-into', { character: '\ead4' }, localize('debug-step-into', ''));
registerIcon('debug-step-out', { character: '\ead5' }, localize('debug-step-out', ''));
registerIcon('debug-step-over', { character: '\ead6' }, localize('debug-step-over', ''));
registerIcon('debug-stop', { character: '\ead7' }, localize('debug-stop', ''));
registerIcon('debug', { character: '\ead8' }, localize('debug', ''));
registerIcon('device-camera-video', { character: '\ead9' }, localize('device-camera-video', ''));
registerIcon('device-camera', { character: '\eada' }, localize('device-camera', ''));
registerIcon('device-mobile', { character: '\eadb' }, localize('device-mobile', ''));
registerIcon('diff-added', { character: '\eadc' }, localize('diff-added', ''));
registerIcon('diff-ignored', { character: '\eadd' }, localize('diff-ignored', ''));
registerIcon('diff-modified', { character: '\eade' }, localize('diff-modified', ''));
registerIcon('diff-removed', { character: '\eadf' }, localize('diff-removed', ''));
registerIcon('diff-renamed', { character: '\eae0' }, localize('diff-renamed', ''));
registerIcon('diff', { character: '\eae1' }, localize('diff', ''));
registerIcon('discard', { character: '\eae2' }, localize('discard', ''));
registerIcon('editor-layout', { character: '\eae3' }, localize('editor-layout', ''));
registerIcon('empty-window', { character: '\eae4' }, localize('empty-window', ''));
registerIcon('exclude', { character: '\eae5' }, localize('exclude', ''));
registerIcon('extensions', { character: '\eae6' }, localize('extensions', ''));
registerIcon('eye-closed', { character: '\eae7' }, localize('eye-closed', ''));
registerIcon('file-binary', { character: '\eae8' }, localize('file-binary', ''));
registerIcon('file-code', { character: '\eae9' }, localize('file-code', ''));
registerIcon('file-media', { character: '\eaea' }, localize('file-media', ''));
registerIcon('file-pdf', { character: '\eaeb' }, localize('file-pdf', ''));
registerIcon('file-submodule', { character: '\eaec' }, localize('file-submodule', ''));
registerIcon('file-symlink-directory', { character: '\eaed' }, localize('file-symlink-directory', ''));
registerIcon('file-symlink-file', { character: '\eaee' }, localize('file-symlink-file', ''));
registerIcon('file-zip', { character: '\eaef' }, localize('file-zip', ''));
registerIcon('files', { character: '\eaf0' }, localize('files', ''));
registerIcon('filter', { character: '\eaf1' }, localize('filter', ''));
registerIcon('flame', { character: '\eaf2' }, localize('flame', ''));
registerIcon('fold-down', { character: '\eaf3' }, localize('fold-down', ''));
registerIcon('fold-up', { character: '\eaf4' }, localize('fold-up', ''));
registerIcon('fold', { character: '\eaf5' }, localize('fold', ''));
registerIcon('folder-active', { character: '\eaf6' }, localize('folder-active', ''));
registerIcon('folder-opened', { character: '\eaf7' }, localize('folder-opened', ''));
registerIcon('gear', { character: '\eaf8' }, localize('gear', ''));
registerIcon('gift', { character: '\eaf9' }, localize('gift', ''));
registerIcon('gist-secret', { character: '\eafa' }, localize('gist-secret', ''));
registerIcon('gist', { character: '\eafb' }, localize('gist', ''));
registerIcon('git-commit', { character: '\eafc' }, localize('git-commit', ''));
registerIcon('git-compare', { character: '\eafd' }, localize('git-compare', ''));
registerIcon('git-merge', { character: '\eafe' }, localize('git-merge', ''));
registerIcon('github-action', { character: '\eaff' }, localize('github-action', ''));
registerIcon('github-alt', { character: '\eb00' }, localize('github-alt', ''));
registerIcon('globe', { character: '\eb01' }, localize('globe', ''));
registerIcon('grabber', { character: '\eb02' }, localize('grabber', ''));
registerIcon('graph', { character: '\eb03' }, localize('graph', ''));
registerIcon('gripper', { character: '\eb04' }, localize('gripper', ''));
registerIcon('heart', { character: '\eb05' }, localize('heart', ''));
registerIcon('home', { character: '\eb06' }, localize('home', ''));
registerIcon('horizontal-rule', { character: '\eb07' }, localize('horizontal-rule', ''));
registerIcon('hubot', { character: '\eb08' }, localize('hubot', ''));
registerIcon('inbox', { character: '\eb09' }, localize('inbox', ''));
registerIcon('issue-closed', { character: '\eb0a' }, localize('issue-closed', ''));
registerIcon('issue-reopened', { character: '\eb0b' }, localize('issue-reopened', ''));
registerIcon('issues', { character: '\eb0c' }, localize('issues', ''));
registerIcon('italic', { character: '\eb0d' }, localize('italic', ''));
registerIcon('jersey', { character: '\eb0e' }, localize('jersey', ''));
registerIcon('json', { character: '\eb0f' }, localize('json', ''));
registerIcon('kebab-vertical', { character: '\eb10' }, localize('kebab-vertical', ''));
registerIcon('key', { character: '\eb11' }, localize('key', ''));
registerIcon('law', { character: '\eb12' }, localize('law', ''));
registerIcon('lightbulb-autofix', { character: '\eb13' }, localize('lightbulb-autofix', ''));
registerIcon('link-external', { character: '\eb14' }, localize('link-external', ''));
registerIcon('link', { character: '\eb15' }, localize('link', ''));
registerIcon('list-ordered', { character: '\eb16' }, localize('list-ordered', ''));
registerIcon('list-unordered', { character: '\eb17' }, localize('list-unordered', ''));
registerIcon('live-share', { character: '\eb18' }, localize('live-share', ''));
registerIcon('loading', { character: '\eb19' }, localize('loading', ''));
registerIcon('location', { character: '\eb1a' }, localize('location', ''));
registerIcon('mail-read', { character: '\eb1b' }, localize('mail-read', ''));
registerIcon('mail', { character: '\eb1c' }, localize('mail', ''));
registerIcon('markdown', { character: '\eb1d' }, localize('markdown', ''));
registerIcon('megaphone', { character: '\eb1e' }, localize('megaphone', ''));
registerIcon('mention', { character: '\eb1f' }, localize('mention', ''));
registerIcon('milestone', { character: '\eb20' }, localize('milestone', ''));
registerIcon('mortar-board', { character: '\eb21' }, localize('mortar-board', ''));
registerIcon('move', { character: '\eb22' }, localize('move', ''));
registerIcon('multiple-windows', { character: '\eb23' }, localize('multiple-windows', ''));
registerIcon('mute', { character: '\eb24' }, localize('mute', ''));
registerIcon('no-newline', { character: '\eb25' }, localize('no-newline', ''));
registerIcon('note', { character: '\eb26' }, localize('note', ''));
registerIcon('octoface', { character: '\eb27' }, localize('octoface', ''));
registerIcon('open-preview', { character: '\eb28' }, localize('open-preview', ''));
registerIcon('package', { character: '\eb29' }, localize('package', ''));
registerIcon('paintcan', { character: '\eb2a' }, localize('paintcan', ''));
registerIcon('pin', { character: '\eb2b' }, localize('pin', ''));
registerIcon('play', { character: '\eb2c' }, localize('play', ''));
registerIcon('run', { character: '\eb2c' }, localize('run', ''));
registerIcon('plug', { character: '\eb2d' }, localize('plug', ''));
registerIcon('preserve-case', { character: '\eb2e' }, localize('preserve-case', ''));
registerIcon('preview', { character: '\eb2f' }, localize('preview', ''));
registerIcon('project', { character: '\eb30' }, localize('project', ''));
registerIcon('pulse', { character: '\eb31' }, localize('pulse', ''));
registerIcon('question', { character: '\eb32' }, localize('question', ''));
registerIcon('quote', { character: '\eb33' }, localize('quote', ''));
registerIcon('radio-tower', { character: '\eb34' }, localize('radio-tower', ''));
registerIcon('reactions', { character: '\eb35' }, localize('reactions', ''));
registerIcon('references', { character: '\eb36' }, localize('references', ''));
registerIcon('refresh', { character: '\eb37' }, localize('refresh', ''));
registerIcon('regex', { character: '\eb38' }, localize('regex', ''));
registerIcon('remote-explorer', { character: '\eb39' }, localize('remote-explorer', ''));
registerIcon('remote', { character: '\eb3a' }, localize('remote', ''));
registerIcon('remove', { character: '\eb3b' }, localize('remove', ''));
registerIcon('replace-all', { character: '\eb3c' }, localize('replace-all', ''));
registerIcon('replace', { character: '\eb3d' }, localize('replace', ''));
registerIcon('repo-clone', { character: '\eb3e' }, localize('repo-clone', ''));
registerIcon('repo-force-push', { character: '\eb3f' }, localize('repo-force-push', ''));
registerIcon('repo-pull', { character: '\eb40' }, localize('repo-pull', ''));
registerIcon('repo-push', { character: '\eb41' }, localize('repo-push', ''));
registerIcon('report', { character: '\eb42' }, localize('report', ''));
registerIcon('request-changes', { character: '\eb43' }, localize('request-changes', ''));
registerIcon('rocket', { character: '\eb44' }, localize('rocket', ''));
registerIcon('root-folder-opened', { character: '\eb45' }, localize('root-folder-opened', ''));
registerIcon('root-folder', { character: '\eb46' }, localize('root-folder', ''));
registerIcon('rss', { character: '\eb47' }, localize('rss', ''));
registerIcon('ruby', { character: '\eb48' }, localize('ruby', ''));
registerIcon('save-all', { character: '\eb49' }, localize('save-all', ''));
registerIcon('save-as', { character: '\eb4a' }, localize('save-as', ''));
registerIcon('save', { character: '\eb4b' }, localize('save', ''));
registerIcon('screen-full', { character: '\eb4c' }, localize('screen-full', ''));
registerIcon('screen-normal', { character: '\eb4d' }, localize('screen-normal', ''));
registerIcon('search-stop', { character: '\eb4e' }, localize('search-stop', ''));
registerIcon('server', { character: '\eb50' }, localize('server', ''));
registerIcon('settings-gear', { character: '\eb51' }, localize('settings-gear', ''));
registerIcon('settings', { character: '\eb52' }, localize('settings', ''));
registerIcon('shield', { character: '\eb53' }, localize('shield', ''));
registerIcon('smiley', { character: '\eb54' }, localize('smiley', ''));
registerIcon('sort-precedence', { character: '\eb55' }, localize('sort-precedence', ''));
registerIcon('split-horizontal', { character: '\eb56' }, localize('split-horizontal', ''));
registerIcon('split-vertical', { character: '\eb57' }, localize('split-vertical', ''));
registerIcon('squirrel', { character: '\eb58' }, localize('squirrel', ''));
registerIcon('star-full', { character: '\eb59' }, localize('star-full', ''));
registerIcon('star-half', { character: '\eb5a' }, localize('star-half', ''));
registerIcon('symbol-class', { character: '\eb5b' }, localize('symbol-class', ''));
registerIcon('symbol-color', { character: '\eb5c' }, localize('symbol-color', ''));
registerIcon('symbol-constant', { character: '\eb5d' }, localize('symbol-constant', ''));
registerIcon('symbol-enum-member', { character: '\eb5e' }, localize('symbol-enum-member', ''));
registerIcon('symbol-field', { character: '\eb5f' }, localize('symbol-field', ''));
registerIcon('symbol-file', { character: '\eb60' }, localize('symbol-file', ''));
registerIcon('symbol-interface', { character: '\eb61' }, localize('symbol-interface', ''));
registerIcon('symbol-keyword', { character: '\eb62' }, localize('symbol-keyword', ''));
registerIcon('symbol-misc', { character: '\eb63' }, localize('symbol-misc', ''));
registerIcon('symbol-operator', { character: '\eb64' }, localize('symbol-operator', ''));
registerIcon('symbol-property', { character: '\eb65' }, localize('symbol-property', ''));
registerIcon('wrench', { character: '\eb65' }, localize('wrench', ''));
registerIcon('wrench-subaction', { character: '\eb65' }, localize('wrench-subaction', ''));
registerIcon('symbol-snippet', { character: '\eb66' }, localize('symbol-snippet', ''));
registerIcon('tasklist', { character: '\eb67' }, localize('tasklist', ''));
registerIcon('telescope', { character: '\eb68' }, localize('telescope', ''));
registerIcon('text-size', { character: '\eb69' }, localize('text-size', ''));
registerIcon('three-bars', { character: '\eb6a' }, localize('three-bars', ''));
registerIcon('thumbsdown', { character: '\eb6b' }, localize('thumbsdown', ''));
registerIcon('thumbsup', { character: '\eb6c' }, localize('thumbsup', ''));
registerIcon('tools', { character: '\eb6d' }, localize('tools', ''));
registerIcon('triangle-down', { character: '\eb6e' }, localize('triangle-down', ''));
registerIcon('triangle-left', { character: '\eb6f' }, localize('triangle-left', ''));
registerIcon('triangle-right', { character: '\eb70' }, localize('triangle-right', ''));
registerIcon('triangle-up', { character: '\eb71' }, localize('triangle-up', ''));
registerIcon('twitter', { character: '\eb72' }, localize('twitter', ''));
registerIcon('unfold', { character: '\eb73' }, localize('unfold', ''));
registerIcon('unlock', { character: '\eb74' }, localize('unlock', ''));
registerIcon('unmute', { character: '\eb75' }, localize('unmute', ''));
registerIcon('unverified', { character: '\eb76' }, localize('unverified', ''));
registerIcon('verified', { character: '\eb77' }, localize('verified', ''));
registerIcon('versions', { character: '\eb78' }, localize('versions', ''));
registerIcon('vm-active', { character: '\eb79' }, localize('vm-active', ''));
registerIcon('vm-outline', { character: '\eb7a' }, localize('vm-outline', ''));
registerIcon('vm-running', { character: '\eb7b' }, localize('vm-running', ''));
registerIcon('watch', { character: '\eb7c' }, localize('watch', ''));
registerIcon('whitespace', { character: '\eb7d' }, localize('whitespace', ''));
registerIcon('whole-word', { character: '\eb7e' }, localize('whole-word', ''));
registerIcon('window', { character: '\eb7f' }, localize('window', ''));
registerIcon('word-wrap', { character: '\eb80' }, localize('word-wrap', ''));
registerIcon('zoom-in', { character: '\eb81' }, localize('zoom-in', ''));
registerIcon('zoom-out', { character: '\eb82' }, localize('zoom-out', ''));
registerIcon('list-filter', { character: '\eb83' }, localize('list-filter', ''));
registerIcon('list-flat', { character: '\eb84' }, localize('list-flat', ''));
registerIcon('list-selection', { character: '\eb85' }, localize('list-selection', ''));
registerIcon('selection', { character: '\eb85' }, localize('selection', ''));
registerIcon('list-tree', { character: '\eb86' }, localize('list-tree', ''));
registerIcon('debug-breakpoint-function-unverified', { character: '\eb87' }, localize('debug-breakpoint-function-unverified', ''));
registerIcon('debug-breakpoint-function', { character: '\eb88' }, localize('debug-breakpoint-function', ''));
registerIcon('debug-breakpoint-function-disabled', { character: '\eb88' }, localize('debug-breakpoint-function-disabled', ''));
registerIcon('debug-stackframe-active', { character: '\eb89' }, localize('debug-stackframe-active', ''));
registerIcon('debug-stackframe-dot', { character: '\eb8a' }, localize('debug-stackframe-dot', ''));
registerIcon('debug-stackframe', { character: '\eb8b' }, localize('debug-stackframe', ''));
registerIcon('debug-stackframe-focused', { character: '\eb8b' }, localize('debug-stackframe-focused', ''));
registerIcon('debug-breakpoint-unsupported', { character: '\eb8c' }, localize('debug-breakpoint-unsupported', ''));
registerIcon('symbol-string', { character: '\eb8d' }, localize('symbol-string', ''));
registerIcon('debug-reverse-continue', { character: '\eb8e' }, localize('debug-reverse-continue', ''));
registerIcon('debug-step-back', { character: '\eb8f' }, localize('debug-step-back', ''));
registerIcon('debug-restart-frame', { character: '\eb90' }, localize('debug-restart-frame', ''));
registerIcon('debug-alternate', { character: '\eb91' }, localize('debug-alternate', ''));
registerIcon('call-incoming', { character: '\eb92' }, localize('call-incoming', ''));
registerIcon('call-outgoing', { character: '\eb93' }, localize('call-outgoing', ''));
registerIcon('menu', { character: '\eb94' }, localize('menu', ''));
registerIcon('expand-all', { character: '\eb95' }, localize('expand-all', ''));
registerIcon('feedback', { character: '\eb96' }, localize('feedback', ''));
registerIcon('group-by-ref-type', { character: '\eb97' }, localize('group-by-ref-type', ''));
registerIcon('ungroup-by-ref-type', { character: '\eb98' }, localize('ungroup-by-ref-type', ''));
registerIcon('bell-dot', { character: '\f101' }, localize('bell-dot', ''));
registerIcon('debug-alt-2', { character: '\f102' }, localize('debug-alt-2', ''));
registerIcon('debug-alt', { character: '\f103' }, localize('debug-alt', ''));
// setTimeout(_ => console.log(colorRegistry.toString()), 5000);

View file

@ -7,11 +7,10 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname } from 'vs/base/common/resources';
import { toLocalISOString } from 'vs/base/common/date';
import { ThrottledDelayer, CancelablePromise } from 'vs/base/common/async';
import { CancelablePromise } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ParseError, parse } from 'vs/base/common/json';
@ -19,6 +18,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isString } from 'vs/base/common/types';
type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@ -44,7 +44,6 @@ function isSyncData(thing: any): thing is ISyncData {
export abstract class AbstractSynchroniser extends Disposable {
protected readonly syncFolder: URI;
private cleanUpDelayer: ThrottledDelayer<void>;
private _status: SyncStatus = SyncStatus.Idle;
get status(): SyncStatus { return this._status; }
@ -58,9 +57,11 @@ export abstract class AbstractSynchroniser extends Disposable {
constructor(
readonly source: SyncSource,
readonly resourceKey: ResourceKey,
@IFileService protected readonly fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
@ -68,9 +69,7 @@ export abstract class AbstractSynchroniser extends Disposable {
) {
super();
this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${source}.json`);
this.cleanUpDelayer = new ThrottledDelayer(50);
this.cleanUpBackup();
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resourceKey}.json`);
}
protected setStatus(status: SyncStatus): void {
@ -153,10 +152,18 @@ export abstract class AbstractSynchroniser extends Disposable {
return !!lastSyncData;
}
async getRemoteContent(): Promise<string | null> {
const lastSyncData = await this.getLastSyncUserData();
const { syncData } = await this.getRemoteUserData(lastSyncData);
return syncData ? syncData.content : null;
async getRemoteContentFromPreview(): Promise<string | null> {
return null;
}
async getRemoteContent(ref?: string): Promise<string | null> {
const refOrLastSyncUserData: string | IRemoteUserData | null = ref || await this.getLastSyncUserData();
const { content } = await this.getUserData(refOrLastSyncUserData);
return content;
}
async getLocalBackupContent(ref?: string): Promise<string | null> {
return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref);
}
async resetLocal(): Promise<void> {
@ -192,76 +199,51 @@ export abstract class AbstractSynchroniser extends Disposable {
}
protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null;
const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
const { ref, content } = await this.getUserData(lastSyncData);
let syncData: ISyncData | null = null;
if (content !== null) {
try {
syncData = <ISyncData>JSON.parse(content);
// Migration from old content to sync data
if (!isSyncData(syncData)) {
syncData = { version: this.version, content };
}
} catch (e) {
this.logService.error(e);
}
syncData = this.parseSyncData(content);
}
return { ref, syncData };
}
protected parseSyncData(content: string): ISyncData | null {
let syncData: ISyncData | null = null;
try {
syncData = <ISyncData>JSON.parse(content);
// Migration from old content to sync data
if (!isSyncData(syncData)) {
syncData = { version: this.version, content };
}
} catch (e) {
this.logService.error(e);
}
return syncData;
}
private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise<IUserData> {
if (isString(refOrLastSyncData)) {
const content = await this.userDataSyncStoreService.resolveContent(this.resourceKey, refOrLastSyncData);
return { ref: refOrLastSyncData, content };
} else {
const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null;
return this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
}
}
protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
const syncData: ISyncData = { version: this.version, content };
ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source);
return { ref, syncData };
}
protected async backupLocal(content: VSBuffer): Promise<void> {
const resource = joinPath(this.syncFolder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`);
try {
await this.fileService.writeFile(resource, content);
} catch (e) {
this.logService.error(e);
}
this.cleanUpDelayer.trigger(() => this.cleanUpBackup());
protected async backupLocal(content: string): Promise<void> {
const syncData: ISyncData = { version: this.version, content };
return this.userDataSyncBackupStoreService.backup(this.resourceKey, JSON.stringify(syncData));
}
private async cleanUpBackup(): Promise<void> {
try {
if (!(await this.fileService.exists(this.syncFolder))) {
return;
}
const stat = await this.fileService.resolve(this.syncFolder);
if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort();
const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue<number>('sync.localBackupDuration') || 30 /* Default 30 days */);
let toDelete = all.filter(stat => {
const ctime = stat.ctime || new Date(
parseInt(stat.name.substring(0, 4)),
parseInt(stat.name.substring(4, 6)) - 1,
parseInt(stat.name.substring(6, 8)),
parseInt(stat.name.substring(9, 11)),
parseInt(stat.name.substring(11, 13)),
parseInt(stat.name.substring(13, 15))
).getTime();
return Date.now() - ctime > backUpMaxAge;
});
const remaining = all.length - toDelete.length;
if (remaining < 10) {
toDelete = toDelete.slice(10 - remaining);
}
await Promise.all(toDelete.map(stat => {
this.logService.info('Deleting from backup', stat.resource.path);
this.fileService.del(stat.resource);
}));
}
} catch (e) {
this.logService.error(e);
}
}
abstract readonly resourceKey: ResourceKey;
protected abstract readonly version: number;
protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus>;
}
@ -283,15 +265,17 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
constructor(
protected readonly file: URI,
source: SyncSource,
resourceKey: ResourceKey,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
super(source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(file)));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
@ -305,14 +289,12 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
this.setStatus(SyncStatus.Idle);
}
async getRemoteContent(preview?: boolean): Promise<string | null> {
if (preview) {
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
}
async getRemoteContentFromPreview(): Promise<string | null> {
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
}
return super.getRemoteContent();
return null;
}
protected async getLocalFileContent(): Promise<IFileContent | null> {
@ -327,7 +309,6 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
try {
if (oldContent) {
// file exists already
await this.backupLocal(oldContent.value);
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent);
} else {
// file does not exist
@ -382,16 +363,18 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
constructor(
file: URI,
source: SyncSource,
resourceKey: ResourceKey,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
super(file, source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
}
protected hasErrors(content: string): boolean {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@ -16,7 +16,6 @@ import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { VSBuffer } from 'vs/base/common/buffer';
interface ISyncPreviewResult {
readonly localExtensions: ISyncExtension[];
@ -35,7 +34,6 @@ interface ILastSyncUserData extends IRemoteUserData {
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'extensions';
protected readonly version: number = 2;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
@ -43,6 +41,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@ -51,7 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
super(SyncSource.Extensions, 'extensions', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
this._register(
Event.debounce(
Event.any<any>(
@ -121,6 +120,33 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
async stop(): Promise<void> { }
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
const content = await super.getRemoteContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getLocalBackupContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
private getFragment(content: string, fragment: string): string | null {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (fragment) {
case 'extensions':
return syncData.content;
}
}
return null;
}
accept(content: string): Promise<void> {
throw new Error('Extensions: Conflicts should not occur');
}
@ -137,10 +163,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return false;
}
async getRemoteContent(): Promise<string | null> {
return null;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<SyncStatus> {
const previewResult = await this.getPreview(remoteUserData, lastSyncUserData);
await this.apply(previewResult);
@ -180,7 +202,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (added.length || removed.length || updated.length) {
// back up all disabled or market place extensions
const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid);
await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions, null, '\t')));
await this.backupLocal(JSON.stringify(backUpExtensions));
skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions);
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -29,19 +29,19 @@ interface ISyncPreviewResult {
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'globalState';
protected readonly version: number = 1;
constructor(
@IFileService fileService: IFileService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
super(SyncSource.GlobalState, 'globalState', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
}
@ -104,6 +104,33 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
async stop(): Promise<void> { }
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getRemoteContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getLocalBackupContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
private getFragment(content: string, fragment: string): string | null {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (fragment) {
case 'globalState':
return syncData.content;
}
}
return null;
}
accept(content: string): Promise<void> {
throw new Error('UI State: Conflicts should not occur');
}
@ -120,17 +147,13 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return false;
}
async getRemoteContent(): Promise<string | null> {
return null;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
const result = await this.getPreview(remoteUserData, lastSyncUserData);
await this.apply(result);
return SyncStatus.Idle;
}
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null,): Promise<ISyncPreviewResult> {
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
@ -158,7 +181,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
if (local) {
// update local
this.logService.trace('UI State: Updating local ui state...');
await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData, null, '\t')));
await this.backupLocal(JSON.stringify(localUserData));
await this.writeLocalGlobalState(local);
this.logService.info('UI State: Updated local ui state');
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json';
@ -29,12 +29,12 @@ interface ISyncContent {
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'keybindings';
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
protected readonly version: number = 1;
constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@ -43,7 +43,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
super(environmentService.keybindingsResource, SyncSource.Keybindings, 'keybindings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
}
async pull(): Promise<void> {
@ -156,11 +156,38 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return false;
}
async getRemoteContent(preview?: boolean): Promise<string | null> {
const content = await super.getRemoteContent(preview);
async getRemoteContentFromPreview(): Promise<string | null> {
const content = await super.getRemoteContentFromPreview();
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
}
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
const content = await super.getRemoteContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getLocalBackupContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
private getFragment(content: string, fragment: string): string | null {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (fragment) {
case 'keybindings':
return this.getKeybindingsContentFromSyncContent(syncData.content);
}
}
return null;
}
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
try {
const result = await this.getPreview(remoteUserData, lastSyncUserData);
@ -197,13 +224,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (hasLocalChanged) {
this.logService.trace('Keybindings: Updating local keybindings...');
await this.backupLocal(this.toSyncContent(content, null));
await this.updateLocalFileContent(content, fileContent);
this.logService.info('Keybindings: Updated local keybindings');
}
if (hasRemoteChanged) {
this.logService.trace('Keybindings: Updating remote keybindings...');
const remoteContents = this.updateSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null);
const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null);
remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref);
this.logService.info('Keybindings: Updated remote keybindings');
}
@ -218,7 +246,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) {
this.logService.trace('Keybindings: Updating last synchronized keybindings...');
const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null);
const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null);
await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } });
this.logService.info('Keybindings: Updated last synchronized keybindings');
}
@ -301,7 +329,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
}
private updateSyncContent(keybindingsContent: string, syncContent: string | null): string {
private toSyncContent(keybindingsContent: string, syncContent: string | null): string {
let parsed: ISyncContent = {};
try {
parsed = JSON.parse(syncContent || '{}');

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json';
import { localize } from 'vs/nls';
@ -37,7 +37,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
_serviceBrand: any;
readonly resourceKey: ResourceKey = 'settings';
protected readonly version: number = 1;
protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; }
@ -50,6 +49,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
@IFileService fileService: IFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
@IConfigurationService configurationService: IConfigurationService,
@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
) {
super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
super(environmentService.settingsResource, SyncSource.Settings, 'settings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
}
protected setStatus(status: SyncStatus): void {
@ -187,13 +187,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return false;
}
async getRemoteContent(preview?: boolean): Promise<string | null> {
let content = await super.getRemoteContent(preview);
async getRemoteContentFromPreview(): Promise<string | null> {
let content = await super.getRemoteContentFromPreview();
if (content !== null) {
const settingsSyncContent = this.parseSettingsSyncContent(content);
content = settingsSyncContent ? settingsSyncContent.settings : null;
}
if (preview && content !== null) {
if (content !== null) {
const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the remote content for preview
const ignoredSettings = await this.getIgnoredSettings();
@ -202,6 +202,36 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return content;
}
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getRemoteContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
let content = await super.getLocalBackupContent(ref);
if (content !== null && fragment) {
return this.getFragment(content, fragment);
}
return content;
}
private getFragment(content: string, fragment: string): string | null {
const syncData = this.parseSyncData(content);
if (syncData) {
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
if (settingsSyncContent) {
switch (fragment) {
case 'settings':
return settingsSyncContent.settings;
}
}
}
return null;
}
async accept(content: string): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
const preview = await this.syncPreviewResultPromise!;
@ -259,6 +289,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (hasLocalChanged) {
this.logService.trace('Settings: Updating local settings...');
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(content)));
await this.updateLocalFileContent(content, fileContent);
this.logService.info('Settings: Updated local settings');
}
@ -269,7 +300,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const ignoredSettings = await this.getIgnoredSettings(content);
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils);
this.logService.trace('Settings: Updating remote settings...');
remoteUserData = await this.updateRemoteUserData(JSON.stringify(<ISettingsSyncContent>{ settings: content }), forcePush ? null : remoteUserData.ref);
remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), forcePush ? null : remoteUserData.ref);
this.logService.info('Settings: Updated remote settings');
}
@ -354,6 +385,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return null;
}
private toSettingsSyncContent(settings: string): ISettingsSyncContent {
return { settings };
}
private _defaultIgnoredSettings: Promise<string[]> | undefined = undefined;
protected async getIgnoredSettings(content?: string): Promise<string[]> {
if (!this._defaultIgnoredSettings) {

View file

@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { URI } from 'vs/base/common/uri';
import { isEqual, joinPath } from 'vs/base/common/resources';
import { isEqual, joinPath, dirname, basename } from 'vs/base/common/resources';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IProductService } from 'vs/platform/product/common/productService';
import { distinct } from 'vs/base/common/arrays';
@ -143,6 +143,11 @@ export interface IUserDataManifest {
session: string;
}
export interface IResourceRefHandle {
ref: string;
created: number;
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
export interface IUserDataSyncStoreService {
_serviceBrand: undefined;
@ -151,11 +156,19 @@ export interface IUserDataSyncStoreService {
write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise<string>;
manifest(): Promise<IUserDataManifest | null>;
clear(): Promise<void>;
getAllRefs(key: ResourceKey): Promise<string[]>;
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]>;
resolveContent(key: ResourceKey, ref: string): Promise<string | null>;
delete(key: ResourceKey): Promise<void>;
}
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
export interface IUserDataSyncBackupStoreService {
_serviceBrand: undefined;
backup(resourceKey: ResourceKey, content: string): Promise<void>;
getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]>;
resolveContent(key: ResourceKey, ref?: string): Promise<string | null>;
}
//#endregion
// #region User Data Sync Error
@ -248,7 +261,9 @@ export interface IUserDataSynchroniser {
hasLocalData(): Promise<boolean>;
resetLocal(): Promise<void>;
getRemoteContent(preivew?: boolean): Promise<string | null>;
getRemoteContentFromPreview(): Promise<string | null>;
getRemoteContent(ref?: string, fragment?: string): Promise<string | null>;
getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null>;
accept(content: string): Promise<void>;
}
@ -293,7 +308,7 @@ export interface IUserDataSyncService {
resetLocal(): Promise<void>;
isFirstTimeSyncWithMerge(): Promise<boolean>;
getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null>;
resolveContent(resource: URI): Promise<string | null>;
accept(source: SyncSource, content: string): Promise<void>;
}
@ -335,12 +350,27 @@ export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncSt
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
export function toRemoteContentResource(source: SyncSource): URI {
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` });
export const PREVIEW_QUERY = 'preview=true';
export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string): URI {
return toRemoteSyncResource(getResourceKeyFromSyncSource(source), ref);
}
export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined {
return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0];
export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI {
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` });
}
export function toLocalBackupSyncResource(resourceKey: ResourceKey, ref?: string): URI {
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resourceKey}/${ref ? ref : 'latest'}` });
}
export function resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null {
const remote = resource.authority === 'remote';
const resourceKey: ResourceKey = basename(dirname(resource)) as ResourceKey;
const ref = basename(resource);
if (resourceKey && ref) {
return { remote, resourceKey, ref: ref !== 'latest' ? ref : undefined };
}
return null;
}
export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined {
if (isEqual(uri, environmentService.settingsSyncPreviewResource)) {
return SyncSource.Settings;
@ -350,3 +380,21 @@ export function getSyncSourceFromPreviewResource(uri: URI, environmentService: I
}
return undefined;
}
export function getResourceKeyFromSyncSource(source: SyncSource): ResourceKey {
switch (source) {
case SyncSource.Settings: return 'settings';
case SyncSource.Keybindings: return 'keybindings';
case SyncSource.Extensions: return 'extensions';
case SyncSource.GlobalState: return 'globalState';
}
}
export function getSyncSourceFromResourceKey(resourceKey: ResourceKey): SyncSource {
switch (resourceKey) {
case 'settings': return SyncSource.Settings;
case 'keybindings': return SyncSource.Keybindings;
case 'extensions': return SyncSource.Extensions;
case 'globalState': return SyncSource.GlobalState;
}
}

View file

@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { joinPath } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { toLocalISOString } from 'vs/base/common/date';
import { VSBuffer } from 'vs/base/common/buffer';
export class UserDataSyncBackupStoreService extends Disposable implements IUserDataSyncBackupStoreService {
_serviceBrand: any;
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
) {
super();
ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey));
}
async getAllRefs(resourceKey: ResourceKey): Promise<IResourceRefHandle[]> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey);
const stat = await this.fileService.resolve(folder);
if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse();
return all.map(stat => ({
ref: stat.name,
created: this.getCreationTime(stat)
}));
}
return [];
}
async resolveContent(resourceKey: ResourceKey, ref?: string): Promise<string | null> {
if (!ref) {
const refs = await this.getAllRefs(resourceKey);
if (refs.length) {
ref = refs[refs.length - 1].ref;
}
}
if (ref) {
const file = joinPath(this.environmentService.userDataSyncHome, resourceKey, ref);
const content = await this.fileService.readFile(file);
return content.value.toString();
}
return null;
}
async backup(resourceKey: ResourceKey, content: string): Promise<void> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey);
const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`);
try {
await this.fileService.writeFile(resource, VSBuffer.fromString(content));
} catch (e) {
this.logService.error(e);
}
try {
this.cleanUpBackup(resourceKey);
} catch (e) { /* Ignore */ }
}
private async cleanUpBackup(resourceKey: ResourceKey): Promise<void> {
const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey);
try {
try {
if (!(await this.fileService.exists(folder))) {
return;
}
} catch (e) {
return;
}
const stat = await this.fileService.resolve(folder);
if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort();
const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue<number>('sync.localBackupDuration') || 30 /* Default 30 days */);
let toDelete = all.filter(stat => Date.now() - this.getCreationTime(stat) > backUpMaxAge);
const remaining = all.length - toDelete.length;
if (remaining < 10) {
toDelete = toDelete.slice(10 - remaining);
}
await Promise.all(toDelete.map(stat => {
this.logService.info('Deleting from backup', stat.resource.path);
this.fileService.del(stat.resource);
}));
}
} catch (e) {
this.logService.error(e);
}
}
private getCreationTime(stat: IFileStat) {
return stat.ctime || new Date(
parseInt(stat.name.substring(0, 4)),
parseInt(stat.name.substring(4, 6)) - 1,
parseInt(stat.name.substring(6, 8)),
parseInt(stat.name.substring(9, 11)),
parseInt(stat.name.substring(11, 13)),
parseInt(stat.name.substring(13, 15))
).getTime();
}
}

View file

@ -5,7 +5,7 @@
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
@ -34,7 +34,7 @@ export class UserDataSyncChannel implements IServerChannel {
case 'stop': this.service.stop(); return Promise.resolve();
case 'reset': return this.service.reset();
case 'resetLocal': return this.service.resetLocal();
case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]);
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge();
}
throw new Error('Invalid call');
@ -67,7 +67,9 @@ export class SettingsSyncChannel implements IServerChannel {
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
case 'hasLocalData': return this.service.hasLocalData();
case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]);
case 'getRemoteContent': return this.service.getRemoteContent(args[0]);
case 'getRemoteContentFromPreview': return this.service.getRemoteContentFromPreview();
case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]);
case 'getLocalBackupContent': return this.service.getLocalBackupContent(args[0], args[1]);
}
throw new Error('Invalid call');
}
@ -148,3 +150,20 @@ export class UserDataSyncStoreServiceChannel implements IServerChannel {
throw new Error('Invalid call');
}
}
export class UserDataSyncBackupStoreServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncBackupStoreService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'getAllRefs': return this.service.getAllRefs(args[0]);
case 'resolveContent': return this.service.resolveContent(args[0], args[1]);
}
throw new Error('Invalid call');
}
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
@ -15,6 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { equals } from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
type SyncErrorClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@ -176,11 +177,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await synchroniser.accept(content);
}
async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> {
await this.checkEnablement();
for (const synchroniser of this.synchronisers) {
if (synchroniser.source === source) {
return synchroniser.getRemoteContent(preview);
async resolveContent(resource: URI): Promise<string | null> {
const result = resolveSyncResource(resource);
if (result) {
const synchronizer = this.synchronisers.filter(s => s.resourceKey === result.resourceKey)[0];
if (synchronizer) {
if (PREVIEW_QUERY === resource.query) {
return result.remote ? synchronizer.getRemoteContentFromPreview() : null;
}
return result.remote ? synchronizer.getRemoteContent(result.ref, resource.fragment) : synchronizer.getLocalBackupContent(result.ref, resource.fragment);
}
}
return null;

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, ResourceKey } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, ResourceKey, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
import { joinPath, relativePath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -31,7 +31,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
}
async getAllRefs(key: ResourceKey): Promise<string[]> {
async getAllRefs(key: ResourceKey): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
}
@ -45,8 +45,8 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined);
}
const resources: string[] = await asJson<string[]>(context) || [];
return resources.map(resource => relativePath(uri, URI.parse(resource))!);
const result = await asJson<{ url: string, created: number }[]>(context) || [];
return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created }));
}
async resolveContent(key: ResourceKey, ref: string): Promise<string | null> {

View file

@ -68,7 +68,7 @@ suite('TestSynchronizer', () => {
teardown(() => disposableStore.clear());
test('status is syncing', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
const actual: SyncStatus[] = [];
disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status)));
@ -85,7 +85,7 @@ suite('TestSynchronizer', () => {
});
test('status is set correctly when sync is finished', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
testObject.syncBarrier.open();
const actual: SyncStatus[] = [];
@ -97,7 +97,7 @@ suite('TestSynchronizer', () => {
});
test('status is set correctly when sync has conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
testObject.syncResult = { status: SyncStatus.HasConflicts };
testObject.syncBarrier.open();
@ -110,7 +110,7 @@ suite('TestSynchronizer', () => {
});
test('status is set correctly when sync has errors', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
testObject.syncResult = { error: true };
testObject.syncBarrier.open();
@ -127,7 +127,7 @@ suite('TestSynchronizer', () => {
});
test('sync should not run if syncing already', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
const promise = Event.toPromise(testObject.onDoSyncCall.event);
testObject.sync();
@ -144,7 +144,7 @@ suite('TestSynchronizer', () => {
});
test('sync should not run if disabled', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resourceKey, false);
const actual: SyncStatus[] = [];
@ -157,7 +157,7 @@ suite('TestSynchronizer', () => {
});
test('sync should not run if there are conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
testObject.syncResult = { status: SyncStatus.HasConflicts };
testObject.syncBarrier.open();
await testObject.sync();
@ -171,7 +171,7 @@ suite('TestSynchronizer', () => {
});
test('request latest data on precondition failure', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings);
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings');
// Sync once
testObject.syncBarrier.open();
await testObject.sync();

View file

@ -6,7 +6,7 @@
import { IRequestService } from 'vs/platform/request/common/request';
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
import { generateUuid } from 'vs/base/common/uuid';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
@ -36,6 +36,7 @@ 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';
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
export class UserDataSyncClient extends Disposable {
@ -89,6 +90,7 @@ export class UserDataSyncClient extends Disposable {
this.instantiationService.stub(IUserDataSyncLogService, logService);
this.instantiationService.stub(ITelemetryService, NullTelemetryService);
this.instantiationService.stub(IUserDataSyncStoreService, this.instantiationService.createInstance(UserDataSyncStoreService));
this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService));
this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService());
this.instantiationService.stub(IUserDataSyncEnablementService, this.instantiationService.createInstance(UserDataSyncEnablementService));

7
src/vs/vscode.d.ts vendored
View file

@ -4975,10 +4975,11 @@ declare module 'vscode' {
color: string | ThemeColor | undefined;
/**
* The identifier of a command to run on click. The command must be
* [known](#commands.getCommands).
* [`Command`](#Command) or identifier of a command to run on click.
*
* The command must be [known](#commands.getCommands).
*/
command: string | undefined;
command: string | Command | undefined;
/**
* Shows the entry in the status bar.

View file

@ -35,7 +35,7 @@ declare module 'vscode' {
readonly added: string[];
/**
* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed..
* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.
*/
readonly removed: string[];
}
@ -43,14 +43,14 @@ declare module 'vscode' {
export interface AuthenticationProvider {
/**
* Used as an identifier for extensions trying to work with a particular
* provider: 'Microsoft', 'GitHub', etc. id must be unique, registering
* provider: 'microsoft', 'github', etc. id must be unique, registering
* another provider with the same id will fail.
*/
readonly id: string;
readonly displayName: string;
/**
* A [enent](#Event) which fires when the array of sessions has changed, or data
* An [event](#Event) which fires when the array of sessions has changed, or data
* within a session has changed.
*/
readonly onDidChangeSessions: Event<void>;
@ -75,7 +75,31 @@ declare module 'vscode' {
*/
export const onDidChangeAuthenticationProviders: Event<AuthenticationProvidersChangeEvent>;
export const providers: ReadonlyArray<AuthenticationProvider>;
/**
* Returns whether a provider with providerId is currently registered.
*/
export function hasProvider(providerId: string): boolean;
/**
* Get existing authentication sessions. Rejects if a provider with providerId is not
* registered, or if the user does not consent to sharing authentication information with
* the extension.
*/
export function getSessions(providerId: string, scopes: string[]): Thenable<readonly AuthenticationSession[]>;
/**
* Prompt a user to login to create a new authenticaiton session. Rejects if a provider with
* providerId is not registered, or if the user does not consent to sharing authentication
* information with the extension.
*/
export function login(providerId: string, scopes: string[]): Thenable<AuthenticationSession>;
/**
* An [event](#Event) which fires when the array of sessions has changed, or data
* within a session has changed for a provider. Fires with the ids of the providers
* that have had session data change.
*/
export const onDidChangeSessions: Event<string[]>;
}
//#endregion
@ -1745,4 +1769,18 @@ declare module 'vscode' {
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/90517
export interface FileSystemError {
/**
* A code that identifies this error.
*
* Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound),
* or `undefined` for an unspecified error.
*/
readonly code?: string;
}
////#endregion
}

View file

@ -8,6 +8,7 @@ import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../commo
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { dispose } from 'vs/base/common/lifecycle';
import { Command } from 'vs/editor/common/modes';
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
export class MainThreadStatusBar implements MainThreadStatusBarShape {
@ -24,7 +25,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
this.entries.clear();
}
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void {
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void {
const entry: IStatusbarEntry = { text, tooltip, command, color };
if (typeof priority === 'undefined') {

View file

@ -33,6 +33,7 @@ import {
RunOptionsDTO
} from 'vs/workbench/api/common/shared/tasks';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
namespace TaskExecutionDTO {
export function from(value: TaskExecution): TaskExecutionDTO {
@ -604,7 +605,7 @@ export class MainThreadTask implements MainThreadTaskShape {
return URI.parse(`${info.scheme}://${info.authority}${path}`);
},
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise<ResolvedVariables> => {
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise<ResolvedVariables> => {
const vars: string[] = [];
toResolve.variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => {
@ -613,7 +614,7 @@ export class MainThreadTask implements MainThreadTaskShape {
partiallyResolvedVars.push(entry.value);
});
return new Promise<ResolvedVariables>((resolve, reject) => {
this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks').then(resolvedVars => {
this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => {
const result: ResolvedVariables = {
process: undefined,
variables: new Map<string, string>()

View file

@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as resources from 'vs/base/common/resources';
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views';
import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView';
import { TreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/treeView';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { coalesce, } from 'vs/base/common/arrays';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
@ -396,7 +396,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
const viewDescriptor = <ICustomViewDescriptor>{
id: item.id,
name: item.name,
ctorDescriptor: new SyncDescriptor(CustomTreeViewPane),
ctorDescriptor: new SyncDescriptor(TreeViewPane),
when: ContextKeyExpr.deserialize(item.when),
canToggleVisibility: true,
canMoveView: true,

View file

@ -143,7 +143,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostClipboard = new ExtHostClipboard(rpcProtocol);
const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService);
const extHostDialogs = new ExtHostDialogs(rpcProtocol);
const extHostStatusBar = new ExtHostStatusBar(rpcProtocol);
const extHostStatusBar = new ExtHostStatusBar(rpcProtocol, extHostCommands.converter);
const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments);
// Register API-ish commands
@ -185,12 +185,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
return extHostAuthentication.registerAuthenticationProvider(provider);
},
get providers() {
return extHostAuthentication.providers(extension);
},
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
return extHostAuthentication.onDidChangeAuthenticationProviders;
}
},
hasProvider(providerId: string): boolean {
return extHostAuthentication.hasProvider(providerId);
},
getSessions(providerId: string, scopes: string[]): Thenable<readonly vscode.AuthenticationSession[]> {
return extHostAuthentication.getSessions(extension, providerId, scopes);
},
login(providerId: string, scopes: string[]): Thenable<vscode.AuthenticationSession> {
return extHostAuthentication.login(extension, providerId, scopes);
},
get onDidChangeSessions(): Event<string[]> {
return extHostAuthentication.onDidChangeSessions;
},
};
// namespace: commands

View file

@ -533,7 +533,7 @@ export interface MainThreadQuickOpenShape extends IDisposable {
}
export interface MainThreadStatusBarShape extends IDisposable {
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void;
$dispose(id: number): void;
}

View file

@ -10,61 +10,6 @@ import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthen
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export class AuthenticationProviderWrapper implements vscode.AuthenticationProvider {
readonly onDidChangeSessions: vscode.Event<void>;
constructor(private _requestingExtension: IExtensionDescription,
private _provider: vscode.AuthenticationProvider,
private _proxy: MainThreadAuthenticationShape) {
this.onDidChangeSessions = this._provider.onDidChangeSessions;
}
get id(): string {
return this._provider.id;
}
get displayName(): string {
return this._provider.displayName;
}
async getSessions(): Promise<ReadonlyArray<vscode.AuthenticationSession>> {
return (await this._provider.getSessions()).map(session => {
return {
id: session.id,
accountName: session.accountName,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
this._provider.id,
this.displayName,
ExtensionIdentifier.toKey(this._requestingExtension.identifier),
this._requestingExtension.displayName || this._requestingExtension.name);
if (!isAllowed) {
throw new Error('User did not consent to token access.');
}
return session.getAccessToken();
}
};
});
}
async login(scopes: string[]): Promise<vscode.AuthenticationSession> {
const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
return this._provider.login(scopes);
}
logout(sessionId: string): Thenable<void> {
return this._provider.logout(sessionId);
}
}
export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private _proxy: MainThreadAuthenticationShape;
private _authenticationProviders: Map<string, vscode.AuthenticationProvider> = new Map<string, vscode.AuthenticationProvider>();
@ -72,14 +17,60 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private _onDidChangeAuthenticationProviders = new Emitter<vscode.AuthenticationProvidersChangeEvent>();
readonly onDidChangeAuthenticationProviders: Event<vscode.AuthenticationProvidersChangeEvent> = this._onDidChangeAuthenticationProviders.event;
private _onDidChangeSessions = new Emitter<string[]>();
readonly onDidChangeSessions: Event<string[]> = this._onDidChangeSessions.event;
constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
}
providers(requestingExtension: IExtensionDescription): vscode.AuthenticationProvider[] {
let providers: vscode.AuthenticationProvider[] = [];
this._authenticationProviders.forEach(provider => providers.push(new AuthenticationProviderWrapper(requestingExtension, provider, this._proxy)));
return providers;
hasProvider(providerId: string): boolean {
return !!this._authenticationProviders.get(providerId);
}
async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<readonly vscode.AuthenticationSession[]> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const orderedScopes = scopes.sort().join(' ');
return (await provider.getSessions())
.filter(session => session.scopes.sort().join(' ') === orderedScopes)
.map(session => {
return {
id: session.id,
accountName: session.accountName,
scopes: session.scopes,
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
provider.displayName,
ExtensionIdentifier.toKey(requestingExtension.identifier),
requestingExtension.displayName || requestingExtension.name);
if (!isAllowed) {
throw new Error('User did not consent to token access.');
}
return session.getAccessToken();
}
};
});
}
async login(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
const provider = this._authenticationProviders.get(providerId);
if (!provider) {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}
const isAllowed = await this._proxy.$loginPrompt(provider.id, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
return provider.login(scopes);
}
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
@ -91,6 +82,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
const listener = provider.onDidChangeSessions(_ => {
this._proxy.$onDidChangeSessions(provider.id);
this._onDidChangeSessions.fire([provider.id]);
});
this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName);

View file

@ -252,7 +252,15 @@ export class ExtensionsActivator {
return;
}
const currentExtension = this._registry.getExtensionDescription(currentActivation.id)!;
const currentExtension = this._registry.getExtensionDescription(currentActivation.id);
if (!currentExtension) {
// Error condition 0: unknown extension
this._host.onExtensionActivationError(currentActivation.id, new MissingDependencyError(currentActivation.id.value));
const error = new Error(`Unknown dependency '${currentActivation.id.value}'`);
this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error));
return;
}
const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
let currentExtensionGetsGreenLight = true;

View file

@ -148,7 +148,16 @@ class ConsumerFileSystem implements vscode.FileSystem {
}
// file system error
throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);
switch (err.name) {
case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message);
case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message);
case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message);
case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message);
case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message);
case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message);
default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode);
}
}
}

View file

@ -5,11 +5,13 @@
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes';
import { StatusBarItem, StatusBarAlignment } from 'vscode';
import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol';
import type * as vscode from 'vscode';
import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from './extHost.protocol';
import { localize } from 'vs/nls';
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { DisposableStore } from 'vs/base/common/lifecycle';
export class ExtHostStatusBarEntry implements StatusBarItem {
export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
private static ID_GEN = 0;
private _id: number;
@ -24,14 +26,20 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
private _text: string = '';
private _tooltip?: string;
private _color?: string | ThemeColor;
private _command?: string;
private readonly _internalCommandRegistration = new DisposableStore();
private _command?: {
readonly fromApi: string | vscode.Command,
readonly internal: ICommandDto,
};
private _timeoutHandle: any;
private _proxy: MainThreadStatusBarShape;
private _commands: CommandsConverter;
constructor(proxy: MainThreadStatusBarShape, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) {
this._id = ExtHostStatusBarEntry.ID_GEN++;
this._proxy = proxy;
this._commands = commands;
this._statusId = id;
this._statusName = name;
this._alignment = alignment;
@ -42,7 +50,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
return this._id;
}
public get alignment(): StatusBarAlignment {
public get alignment(): vscode.StatusBarAlignment {
return this._alignment;
}
@ -62,8 +70,8 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
return this._color;
}
public get command(): string | undefined {
return this._command;
public get command(): string | vscode.Command | undefined {
return this._command?.fromApi;
}
public set text(text: string) {
@ -81,8 +89,25 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
this.update();
}
public set command(command: string | undefined) {
this._command = command;
public set command(command: string | vscode.Command | undefined) {
if (this._command?.fromApi === command) {
return;
}
this._internalCommandRegistration.clear();
if (typeof command === 'string') {
this._command = {
fromApi: command,
internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration),
};
} else if (command) {
this._command = {
fromApi: command,
internal: this._commands.toInternal(command, this._internalCommandRegistration),
};
} else {
this._command = undefined;
}
this.update();
}
@ -109,7 +134,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
this._timeoutHandle = undefined;
// Set to status bar
this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this.command, this.color,
this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color,
this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
this._priority);
}, 0);
@ -123,7 +148,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem {
class StatusBarMessage {
private _item: StatusBarItem;
private _item: vscode.StatusBarItem;
private _messages: { message: string }[] = [];
constructor(statusBar: ExtHostStatusBar) {
@ -161,16 +186,18 @@ class StatusBarMessage {
export class ExtHostStatusBar {
private _proxy: MainThreadStatusBarShape;
private readonly _proxy: MainThreadStatusBarShape;
private readonly _commands: CommandsConverter;
private _statusMessage: StatusBarMessage;
constructor(mainContext: IMainContext) {
constructor(mainContext: IMainContext, commands: CommandsConverter) {
this._proxy = mainContext.getProxy(MainContext.MainThreadStatusBar);
this._commands = commands;
this._statusMessage = new StatusBarMessage(this);
}
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, id, name, alignment, priority);
createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem {
return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority);
}
setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {

View file

@ -2333,9 +2333,13 @@ export class FileSystemError extends Error {
return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable);
}
readonly code?: string;
constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) {
super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage);
this.code = terminator?.name;
// mark the error as file system provider error so that
// we can extract the error code on the receiving side
markAsFileSystemProviderError(this, code);

View file

@ -7,9 +7,10 @@ import * as nls from 'vs/nls';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import * as strings from 'vs/base/common/strings';
import * as resources from 'vs/base/common/resources';
import { isString } from 'vs/base/common/types';
interface IJSONValidationExtensionPoint {
fileMatch: string;
fileMatch: string | string[];
url: string;
}
@ -25,8 +26,11 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IJSONVal
defaultSnippets: [{ body: { fileMatch: '${1:file.json}', url: '${2:url}' } }],
properties: {
fileMatch: {
type: 'string',
description: nls.localize('contributes.jsonValidation.fileMatch', 'The file pattern to match, for example "package.json" or "*.launch".'),
type: ['string', 'array'],
description: nls.localize('contributes.jsonValidation.fileMatch', 'The file pattern (or an array of patterns) to match, for example "package.json" or "*.launch". Exclusion patterns start with \'!\''),
items: {
type: ['string']
}
},
url: {
description: nls.localize('contributes.jsonValidation.url', 'A schema URL (\'http:\', \'https:\') or relative path to the extension folder (\'./\').'),
@ -51,12 +55,12 @@ export class JSONValidationExtensionPoint {
return;
}
extensionValue.forEach(extension => {
if (typeof extension.fileMatch !== 'string') {
collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined"));
if (!isString(extension.fileMatch) && !(Array.isArray(extension.fileMatch) && extension.fileMatch.every(isString))) {
collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined as a string or an array of strings."));
return;
}
let uri = extension.url;
if (typeof extension.url !== 'string') {
if (!isString(uri)) {
collector.error(nls.localize('invalid.url', "'configuration.jsonValidation.url' must be a URL or relative path"));
return;
}

View file

@ -23,9 +23,9 @@
.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Inconsolata, "Courier New", monospace; }
.windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; }
.linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; }
.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; }
.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; }
.linux { --monaco-monospace-font: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; }
/* Global Styles */

View file

@ -56,6 +56,8 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
// Register String Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
@ -359,6 +361,33 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
)
);
// Register Editor Quick Access
const quickAccessRegistry = Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess);
quickAccessRegistry.registerQuickAccessProvider({
ctor: ActiveGroupEditorsByMostRecentlyUsedQuickAccess,
prefix: ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX,
contextKey: editorPickerContextKey,
placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."),
helpEntries: [{ description: nls.localize('activeGroupEditorsByMostRecentlyUsedQuickAccess', "Show Editors in Active Group by Most Recently Used."), needsEditor: false }]
});
quickAccessRegistry.registerQuickAccessProvider({
ctor: AllEditorsByAppearanceQuickAccess,
prefix: AllEditorsByAppearanceQuickAccess.PREFIX,
contextKey: editorPickerContextKey,
placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."),
helpEntries: [{ description: nls.localize('allEditorsByAppearanceQuickAccess', "Show All Opened Editors By Appearance"), needsEditor: false }]
});
quickAccessRegistry.registerQuickAccessProvider({
ctor: AllEditorsByMostRecentlyUsedQuickAccess,
prefix: AllEditorsByMostRecentlyUsedQuickAccess.PREFIX,
contextKey: editorPickerContextKey,
placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."),
helpEntries: [{ description: nls.localize('allEditorsByMostRecentlyUsedQuickAccess', "Show All Opened Editors By Most Recently Used"), needsEditor: false }]
});
// Register Editor Actions
const category = nls.localize('view', "View");
registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);

View file

@ -16,7 +16,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/common/fuzzyScorer';
import { CancellationToken } from 'vs/base/common/cancellation';
export class EditorPickerEntry extends QuickOpenEntryGroup {

View file

@ -0,0 +1,205 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { prepareQuery, IPreparedQuery, ScorerCache, scoreItem, compareItemsByScore, IItemAccessor } from 'vs/base/common/fuzzyScorer';
import { Schemas } from 'vs/base/common/network';
interface IEditorQuickPickItem extends IQuickPickItem, IEditorIdentifier { }
export abstract class BaseEditorQuickAccessProvider implements IQuickAccessProvider {
protected abstract readonly prefix: string;
private editorQuickPickScoringAccessor = new class implements IItemAccessor<IEditorQuickPickItem> {
getItemLabel(entry: IEditorQuickPickItem): string | undefined {
return entry.label;
}
getItemDescription(entry: IEditorQuickPickItem): string | undefined {
return entry.description;
}
getItemPath(entry: IEditorQuickPickItem): string | undefined {
const resource = toResource(entry.editor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource?.scheme === Schemas.file) {
return resource?.fsPath;
}
return resource?.path;
}
};
constructor(
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
@IEditorService protected readonly editorService: IEditorService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService
) {
}
provide(picker: IQuickPick<IEditorQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Add all view items & filter on type
const scorerCache = Object.create(null);
const updatePickerItems = () => picker.items = this.getEditorPickItems(prepareQuery(picker.value.trim().substr(this.prefix.length)), scorerCache);
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Open the picked view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
this.editorGroupService.getGroup(item.groupId)?.openEditor(item.editor);
}
}));
return disposables;
}
private getEditorPickItems(query: IPreparedQuery, scorerCache: ScorerCache): Array<IEditorQuickPickItem | IQuickPickSeparator> {
const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => {
if (!query.value) {
return true;
}
// Score on label and description
const itemScore = scoreItem(entry, query, true, this.editorQuickPickScoringAccessor, scorerCache);
if (!itemScore.score) {
return false;
}
// Apply highlights
entry.highlights = { label: itemScore.labelMatch, description: itemScore.descriptionMatch };
return true;
});
// Sorting
if (query.value) {
const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).map(group => group.id);
filteredEditorEntries.sort((entryA, entryB) => {
if (entryA.groupId !== entryB.groupId) {
return groups.indexOf(entryA.groupId) - groups.indexOf(entryB.groupId); // older groups first
}
return compareItemsByScore(entryA, entryB, query, true, this.editorQuickPickScoringAccessor, scorerCache);
});
}
// Grouping (for more than one group)
const filteredEditorEntriesWithSeparators: Array<IEditorQuickPickItem | IQuickPickSeparator> = [];
if (this.editorGroupService.count > 1) {
let lastGroupId: number | undefined = undefined;
for (const entry of filteredEditorEntries) {
if (typeof lastGroupId !== 'number' || lastGroupId !== entry.groupId) {
const group = this.editorGroupService.getGroup(entry.groupId);
if (group) {
filteredEditorEntriesWithSeparators.push({ type: 'separator', label: group.label });
}
lastGroupId = entry.groupId;
}
filteredEditorEntriesWithSeparators.push(entry);
}
} else {
filteredEditorEntriesWithSeparators.push(...filteredEditorEntries);
}
return filteredEditorEntriesWithSeparators;
}
private doGetEditorPickItems(): Array<IEditorQuickPickItem> {
return this.doGetEditors().map(({ editor, groupId }) => ({
editor,
groupId,
label: editor.isDirty() && !editor.isSaving() ? `$(circle-filled) ${editor.getName()}` : editor.getName(),
ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()),
description: editor.getDescription(),
iconClasses: getIconClasses(this.modelService, this.modeService, toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })),
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor)
}));
}
protected abstract doGetEditors(): IEditorIdentifier[];
}
//#region Active Editor Group Editors by Most Recently Used
export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider {
static PREFIX = 'edt active ';
readonly prefix = ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX;
protected doGetEditors(): IEditorIdentifier[] {
const group = this.editorGroupService.activeGroup;
return group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId: group.id }));
}
}
//#endregion
//#region All Editors by Appearance
export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProvider {
static PREFIX = 'edt ';
readonly prefix = AllEditorsByAppearanceQuickAccess.PREFIX;
protected doGetEditors(): IEditorIdentifier[] {
const entries: IEditorIdentifier[] = [];
for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) {
for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) {
entries.push({ editor, groupId: group.id });
}
}
return entries;
}
}
//#endregion
//#region All Editors by Most Recently Used
export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider {
static PREFIX = 'edt mru ';
readonly prefix = AllEditorsByMostRecentlyUsedQuickAccess.PREFIX;
protected doGetEditors(): IEditorIdentifier[] {
const entries: IEditorIdentifier[] = [];
for (const editor of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
entries.push(editor);
}
return entries;
}
}
//#endregion

Some files were not shown because too many files have changed in this diff Show more