mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge branch 'master' into misolori/scm-icons
This commit is contained in:
commit
75b86ddb70
74
.vscode/searches/es6.code-search
vendored
Normal file
74
.vscode/searches/es6.code-search
vendored
Normal 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 {
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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] });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
BIN
extensions/vscode-colorize-tests/producticons/ElegantIcons.woff
Normal file
BIN
extensions/vscode-colorize-tests/producticons/ElegantIcons.woff
Normal file
Binary file not shown.
3049
extensions/vscode-colorize-tests/producticons/index.html
Normal file
3049
extensions/vscode-colorize-tests/producticons/index.html
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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.
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
3
src/bootstrap-fork.js
vendored
3
src/bootstrap-fork.js
vendored
|
@ -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
23
src/bootstrap.js
vendored
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -248,6 +248,10 @@ export class InputBox extends Widget {
|
|||
}
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return this.ariaLabel;
|
||||
}
|
||||
|
||||
public get mirrorElement(): HTMLElement | undefined {
|
||||
return this.mirror;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) || '';
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
|
@ -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 = '';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 });
|
||||
}));
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -336,6 +336,10 @@ export class CodeLensWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
getItems(): CodeLensItem[] {
|
||||
return this._data;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
206
src/vs/editor/contrib/quickAccess/gotoLine.ts
Normal file
206
src/vs/editor/contrib/quickAccess/gotoLine.ts
Normal 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();
|
||||
}
|
||||
}
|
104
src/vs/editor/contrib/quickAccess/quickAccess.ts
Normal file
104
src/vs/editor/contrib/quickAccess/quickAccess.ts
Normal 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
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 }]
|
||||
});
|
|
@ -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 }]
|
||||
};
|
|
@ -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; }
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -38,7 +38,7 @@ export interface IGrammar {
|
|||
}
|
||||
|
||||
export interface IJSONValidation {
|
||||
fileMatch: string;
|
||||
fileMatch: string | string[];
|
||||
url: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ export interface IssueReporterExtensionData {
|
|||
version: string;
|
||||
id: string;
|
||||
isTheme: boolean;
|
||||
isBuiltin: boolean;
|
||||
displayName: string | undefined;
|
||||
repositoryUrl: string | undefined;
|
||||
bugsUrl: string | undefined;
|
||||
|
|
|
@ -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',
|
||||
|
|
75
src/vs/platform/quickinput/browser/helpQuickAccess.ts
Normal file
75
src/vs/platform/quickinput/browser/helpQuickAccess.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
|
95
src/vs/platform/quickinput/browser/quickAccess.ts
Normal file
95
src/vs/platform/quickinput/browser/quickAccess.ts
Normal 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];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
142
src/vs/platform/quickinput/common/quickAccess.ts
Normal file
142
src/vs/platform/quickinput/common/quickAccess.ts
Normal 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());
|
|
@ -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;
|
||||
}
|
||||
|
|
543
src/vs/platform/theme/common/iconRegistry.ts
Normal file
543
src/vs/platform/theme/common/iconRegistry.ts
Normal 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);
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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 || '{}');
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
7
src/vs/vscode.d.ts
vendored
|
@ -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.
|
||||
|
|
46
src/vs/vscode.proposed.d.ts
vendored
46
src/vs/vscode.proposed.d.ts
vendored
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
205
src/vs/workbench/browser/parts/editor/editorQuickAccess.ts
Normal file
205
src/vs/workbench/browser/parts/editor/editorQuickAccess.ts
Normal 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
Loading…
Reference in a new issue