Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317)

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

View File

@@ -154,6 +154,7 @@
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon {
font-size: 12px;
color: currentColor;
}
.extensions-viewlet.narrow > .extensions .extension > .icon-container,

View File

@@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor {
private static CONFIG_SCHEMA_UPDATE_DELAYER = 500;
private static readonly SUGGESTIONS: string[] = [
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}`
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}`
];
private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean {

View File

@@ -556,8 +556,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
order: 1
});
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Next Search Result', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Previous Search Result', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category);
MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {

View File

@@ -488,12 +488,19 @@ export class FocusNextSearchResultAction extends Action {
static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result");
constructor(id: string, label: string,
@IViewsService private readonly viewsService: IViewsService
@IViewsService private readonly viewsService: IViewsService,
@IEditorService private readonly editorService: IEditorService,
) {
super(id, label);
}
run(): Promise<any> {
async run(): Promise<any> {
const input = this.editorService.activeEditor;
if (input instanceof SearchEditorInput) {
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
return (this.editorService.activeControl as SearchEditor).focusNextResult();
}
return openSearchView(this.viewsService).then(searchView => {
if (searchView) {
searchView.selectNextMatch();
@@ -507,12 +514,19 @@ export class FocusPreviousSearchResultAction extends Action {
static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result");
constructor(id: string, label: string,
@IViewsService private readonly viewsService: IViewsService
@IViewsService private readonly viewsService: IViewsService,
@IEditorService private readonly editorService: IEditorService,
) {
super(id, label);
}
run(): Promise<any> {
async run(): Promise<any> {
const input = this.editorService.activeEditor;
if (input instanceof SearchEditorInput) {
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
return (this.editorService.activeControl as SearchEditor).focusPreviousResult();
}
return openSearchView(this.viewsService).then(searchView => {
if (searchView) {
searchView.selectPreviousMatch();

View File

@@ -181,25 +181,26 @@ export class SearchView extends ViewPane {
this.container = dom.$('.search-view');
// globals
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService);
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService);
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService);
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService);
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService);
this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService);
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService);
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
// scoped
this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container));
const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService);
viewletFocused.set(true);
this.instantiationService = this.instantiationService.createChild(
new ServiceCollection([IContextKeyService, this.contextKeyService]));
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService);
Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true);
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService);
this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService);
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService);
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService);
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService);
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(contextKeyService);
this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService);
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService);
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
this.instantiationService = this.instantiationService.createChild(
new ServiceCollection([IContextKeyService, this.contextKeyService]));
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('search.sortOrder')) {

View File

@@ -13,6 +13,7 @@ export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord
export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
export const InSearchEditor = new RawContextKey<boolean>('inSearchEditor', false);

View File

@@ -25,7 +25,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -146,6 +146,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L }
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
handler: selectAllSearchEditorMatchesCommand
});
CommandsRegistry.registerCommand(
SearchEditorConstants.RerunSearchEditorSearchCommandId,
(accessor: ServicesAccessor) => {

View File

@@ -47,6 +47,8 @@ import { assertIsDefined } from 'vs/base/common/types';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
const FILE_LINE_REGEX = /^(\S.*):$/;
@@ -299,6 +301,42 @@ export class SearchEditor extends BaseTextEditor {
return this.configurationService.getValue<ISearchConfigurationProperties>('search');
}
private iterateThroughMatches(reverse: boolean) {
const model = this.searchResultEditor.getModel();
if (!model) { return; }
const lastLine = model.getLineCount() ?? 1;
const lastColumn = model.getLineLength(lastLine);
const fallbackStart = reverse ? new Position(lastLine, lastColumn) : new Position(1, 1);
const currentPosition = this.searchResultEditor.getSelection()?.getStartPosition() ?? fallbackStart;
const matchRanges = this.getInput()?.getMatchRanges();
if (!matchRanges) { return; }
const matchRange = (reverse ? findPrevRange : findNextRange)(matchRanges, currentPosition);
this.searchResultEditor.setSelection(matchRange);
this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber);
this.searchResultEditor.focus();
}
focusNextResult() {
this.iterateThroughMatches(false);
}
focusPreviousResult() {
this.iterateThroughMatches(true);
}
focusAllResults() {
this.searchResultEditor
.setSelections((this.getInput()?.getMatchRanges() ?? []).map(
range => new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)));
this.searchResultEditor.focus();
}
async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) {
const options = { resetCursor: true, delay: 0, ..._options };
@@ -307,7 +345,7 @@ export class SearchEditor extends BaseTextEditor {
await this.doRunSearch();
this.toggleRunAgainMessage(false);
if (options.resetCursor) {
this.searchResultEditor.setSelection(new Range(1, 1, 1, 1));
this.searchResultEditor.setPosition(new Position(1, 1));
this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 });
}
}, options.delay);
@@ -523,3 +561,24 @@ registerThemingParticipant((theme, collector) => {
});
export const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border."));
function findNextRange(matchRanges: Range[], currentPosition: Position) {
for (const matchRange of matchRanges) {
if (Position.isBefore(currentPosition, matchRange.getStartPosition())) {
return matchRange;
}
}
return matchRanges[0];
}
function findPrevRange(matchRanges: Range[], currentPosition: Position) {
for (let i = matchRanges.length - 1; i >= 0; i--) {
const matchRange = matchRanges[i];
if (Position.isBefore(matchRange.getStartPosition(), currentPosition)) {
{
return matchRange;
}
}
}
return matchRanges[matchRanges.length - 1];
}

View File

@@ -54,6 +54,14 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor
}
};
export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) => {
const editorService = accessor.get(IEditorService);
const input = editorService.activeEditor;
if (input instanceof SearchEditorInput) {
(editorService.activeControl as SearchEditor).focusAllResults();
}
};
export class OpenSearchEditorAction extends Action {

View File

@@ -12,6 +12,7 @@
opacity: 0.5;
position: absolute;
pointer-events: none;
z-index: 1;
}
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label {

View File

@@ -14,6 +14,8 @@ import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineSe
import { TimelinePane } from './timelinePane';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/product/common/product';
export class TimelinePaneDescriptor implements IViewDescriptor {
@@ -42,13 +44,102 @@ configurationRegistry.registerConfiguration({
'timeline.showView': {
type: 'boolean',
description: localize('timeline.showView', "Experimental: When enabled, shows a Timeline view in the Explorer sidebar."),
default: false //product.quality !== 'stable'
default: product.quality !== 'stable'
},
'timeline.excludeSources': {
type: 'array',
description: localize('timeline.excludeSources', "Experimental: An array of Timeline sources that should be excluded from the Timeline view"),
default: null
},
}
});
if (product.quality !== 'stable') {
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
namespace TimelineViewRefreshAction {
export const ID = 'timeline.refresh';
export const LABEL = localize('timeline.refreshView', "Refresh");
export function handler(): ICommandHandler {
return (accessor, arg) => {
const service = accessor.get(ITimelineService);
return service.reset();
};
}
}
CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
// namespace TimelineViewRefreshHardAction {
// export const ID = 'timeline.refreshHard';
// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)");
// export function handler(fetch?: 'all' | 'more'): ICommandHandler {
// return (accessor, arg) => {
// const service = accessor.get(ITimelineService);
// return service.refresh(fetch);
// };
// }
// }
// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
// namespace TimelineViewLoadMoreAction {
// export const ID = 'timeline.loadMore';
// export const LABEL = localize('timeline.loadMoreInView', "Load More");
// export function handler(): ICommandHandler {
// return (accessor, arg) => {
// const service = accessor.get(ITimelineService);
// return service.refresh('more');
// };
// }
// }
// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler());
// namespace TimelineViewLoadAllAction {
// export const ID = 'timeline.loadAll';
// export const LABEL = localize('timeline.loadAllInView', "Load All");
// export function handler(): ICommandHandler {
// return (accessor, arg) => {
// const service = accessor.get(ITimelineService);
// return service.refresh('all');
// };
// }
// }
// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler());
MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
group: 'navigation',
order: 1,
command: {
id: TimelineViewRefreshAction.ID,
title: TimelineViewRefreshAction.LABEL,
icon: { id: 'codicon/refresh' }
}
}));
// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
// group: 'navigation',
// order: 2,
// command: {
// id: TimelineViewLoadMoreAction.ID,
// title: TimelineViewLoadMoreAction.LABEL,
// icon: { id: 'codicon/unfold' }
// },
// alt: {
// id: TimelineViewLoadAllAction.ID,
// title: TimelineViewLoadAllAction.LABEL,
// icon: { id: 'codicon/unfold' }
// }
// }));
registerSingleton(ITimelineService, TimelineService, true);

View File

@@ -8,6 +8,7 @@ import { localize } from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { Iterator } from 'vs/base/common/iterator';
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
@@ -18,9 +19,9 @@ import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/bro
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline';
import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -28,7 +29,6 @@ import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeS
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { basename } from 'vs/base/common/path';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
import { debounce } from 'vs/base/common/decorators';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -40,13 +40,53 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// TODO[ECA]: Localize all the strings
type TreeElement = TimelineItem;
const InitialPageSize = 20;
const SubsequentPageSize = 40;
interface CommandItem {
handle: 'vscode-command:loadMore';
timestamp: number;
label: string;
themeIcon?: { id: string };
description?: string;
detail?: string;
contextValue?: string;
// Make things easier for duck typing
id: undefined;
icon: undefined;
iconDark: undefined;
source: undefined;
}
type TreeElement = TimelineItem | CommandItem;
// function isCommandItem(item: TreeElement | undefined): item is CommandItem {
// return item?.handle.startsWith('vscode-command:') ?? false;
// }
function isLoadMoreCommandItem(item: TreeElement | undefined): item is CommandItem & {
handle: 'vscode-command:loadMore';
} {
return item?.handle === 'vscode-command:loadMore';
}
function isTimelineItem(item: TreeElement | undefined): item is TimelineItem {
return !item?.handle.startsWith('vscode-command:') ?? false;
}
interface TimelineActionContext {
uri: URI | undefined;
item: TreeElement;
}
interface TimelineCursors {
startCursors?: { before: any; after?: any };
endCursors?: { before: any; after?: any };
more: boolean;
}
export class TimelinePane extends ViewPane {
static readonly ID = 'timeline';
static readonly TITLE = localize('timeline', 'Timeline');
@@ -59,8 +99,9 @@ export class TimelinePane extends ViewPane {
private _menus: TimelineMenus;
private _visibilityDisposables: DisposableStore | undefined;
// private _excludedSources: Set<string> | undefined;
private _items: TimelineItem[] = [];
private _excludedSources: Set<string>;
private _cursorsByProvider: Map<string, TimelineCursors> = new Map();
private _items: { element: TreeElement }[] = [];
private _loadingMessageTimer: any | undefined;
private _pendingRequests = new Map<string, TimelineRequest>();
private _uri: URI | undefined;
@@ -87,6 +128,18 @@ export class TimelinePane extends ViewPane {
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
scopedContextKeyService.createKey('view', TimelinePane.ID);
this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources'));
configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this);
}
private onConfigurationChanged(e: IConfigurationChangeEvent) {
if (!e.affectsConfiguration('timeline.excludeSources')) {
return;
}
this._excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources'));
this.loadTimeline(true);
}
private onActiveEditorChanged() {
@@ -105,7 +158,7 @@ export class TimelinePane extends ViewPane {
this._uri = uri;
this._treeRenderer?.setUri(uri);
this.loadTimeline();
this.loadTimeline(true);
}
private onProvidersChanged(e: TimelineProvidersChangeEvent) {
@@ -116,16 +169,20 @@ export class TimelinePane extends ViewPane {
}
if (e.added) {
this.loadTimeline(e.added);
this.loadTimeline(true, e.added);
}
}
private onTimelineChanged(e: TimelineChangeEvent) {
if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
this.loadTimeline([e.id]);
if (e?.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
this.loadTimeline(e.reset ?? false, e?.id === undefined ? undefined : [e.id], { before: !e.reset });
}
}
private onReset() {
this.loadTimeline(true);
}
private _message: string | undefined;
get message(): string | undefined {
return this._message;
@@ -160,22 +217,27 @@ export class TimelinePane extends ViewPane {
DOM.clearNode(this._messageElement);
}
private async loadTimeline(sources?: string[]) {
private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) {
const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize;
// If we have no source, we are reseting all sources, so cancel everything in flight and reset caches
if (sources === undefined) {
this._items.length = 0;
if (reset) {
this._items.length = 0;
this._cursorsByProvider.clear();
if (this._loadingMessageTimer) {
clearTimeout(this._loadingMessageTimer);
this._loadingMessageTimer = undefined;
if (this._loadingMessageTimer) {
clearTimeout(this._loadingMessageTimer);
this._loadingMessageTimer = undefined;
}
for (const { tokenSource } of this._pendingRequests.values()) {
tokenSource.dispose(true);
}
this._pendingRequests.clear();
}
for (const { tokenSource } of this._pendingRequests.values()) {
tokenSource.dispose(true);
}
this._pendingRequests.clear();
// TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way?
if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) {
this.message = 'The active editor cannot provide timeline information.';
@@ -184,7 +246,7 @@ export class TimelinePane extends ViewPane {
return;
}
if (this._uri !== undefined) {
if (reset && this._uri !== undefined) {
this._loadingMessageTimer = setTimeout((uri: URI) => {
if (uri !== this._uri) {
return;
@@ -200,50 +262,169 @@ export class TimelinePane extends ViewPane {
return;
}
for (const source of sources ?? this.timelineService.getSources()) {
const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s));
if (filteredSources.length === 0) {
if (reset) {
this.refresh();
}
return;
}
let lastIndex = this._items.length - 1;
let lastItem = this._items[lastIndex]?.element;
if (isLoadMoreCommandItem(lastItem)) {
lastItem.themeIcon = { id: 'sync~spin' };
// this._items.splice(lastIndex, 1);
lastIndex--;
if (!reset && !options.before) {
lastItem = this._items[lastIndex]?.element;
const selection = [lastItem];
this._tree.setSelection(selection);
this._tree.setFocus(selection);
}
}
for (const source of filteredSources) {
let request = this._pendingRequests.get(source);
request?.tokenSource.dispose(true);
request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!;
const cursors = this._cursorsByProvider.get(source);
if (!reset) {
// TODO: Handle pending request
this._pendingRequests.set(source, request);
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
if (cursors?.more === false) {
continue;
}
const reusingToken = request?.tokenSource !== undefined;
request = this.timelineService.getTimeline(
source, this._uri,
{
cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after,
...options,
limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize
},
request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true }
)!;
this._pendingRequests.set(source, request);
if (!reusingToken) {
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
}
} else {
request?.tokenSource.dispose(true);
request = this.timelineService.getTimeline(
source, this._uri,
{
...options,
limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize
},
new CancellationTokenSource(), { cacheResults: true }
)!;
this._pendingRequests.set(source, request);
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
}
this.handleRequest(request);
}
}
private async handleRequest(request: TimelineRequest) {
let items;
let timeline: Timeline | undefined;
try {
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? []));
timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result);
}
finally {
this._pendingRequests.delete(request.source);
}
catch { }
this._pendingRequests.delete(request.source);
if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) {
if (
timeline === undefined ||
request.tokenSource.token.isCancellationRequested ||
request.uri !== this._uri
) {
return;
}
this.replaceItems(request.source, items);
}
let items: TreeElement[];
private replaceItems(source: string, items?: TimelineItem[]) {
const hasItems = this._items.length !== 0;
const source = request.source;
if (items?.length) {
this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items);
this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
if (timeline !== undefined) {
if (timeline.paging !== undefined) {
let cursors = this._cursorsByProvider.get(timeline.source ?? source);
if (cursors === undefined) {
cursors = { startCursors: timeline.paging.cursors, more: timeline.paging.more ?? false };
this._cursorsByProvider.set(timeline.source, cursors);
} else {
if (request.options.before) {
if (cursors.endCursors === undefined) {
cursors.endCursors = cursors.startCursors;
}
cursors.startCursors = timeline.paging.cursors;
}
else {
if (cursors.startCursors === undefined) {
cursors.startCursors = timeline.paging.cursors;
}
cursors.endCursors = timeline.paging.cursors;
}
cursors.more = timeline.paging.more ?? true;
}
}
} else {
this._cursorsByProvider.delete(source);
}
else if (this._items.length && this._items.some(i => i.source === source)) {
this._items = this._items.filter(i => i.source !== source);
items = (timeline.items as TreeElement[]) ?? [];
const alreadyHadItems = this._items.length !== 0;
let changed;
if (request.options.cursor) {
changed = this.mergeItems(request.source, items, request.options);
} else {
changed = this.replaceItems(request.source, items);
}
else {
if (!changed) {
// If there are no items at all and no pending requests, make sure to refresh (to show the no timeline info message)
if (this._items.length === 0 && this._pendingRequests.size === 0) {
this.refresh();
}
return;
}
if (this._pendingRequests.size === 0 && this._items.length !== 0) {
const lastIndex = this._items.length - 1;
const lastItem = this._items[lastIndex]?.element;
if (timeline.paging?.more || Iterator.some(this._cursorsByProvider.values(), cursors => cursors.more)) {
if (isLoadMoreCommandItem(lastItem)) {
lastItem.themeIcon = undefined;
}
else {
this._items.push({
element: {
handle: 'vscode-command:loadMore',
label: 'Load more',
timestamp: 0
} as CommandItem
});
}
}
else {
if (isLoadMoreCommandItem(lastItem)) {
this._items.splice(lastIndex, 1);
}
}
}
// If we have items already and there are other pending requests, debounce for a bit to wait for other requests
if (hasItems && this._pendingRequests.size !== 0) {
if (alreadyHadItems && this._pendingRequests.size !== 0) {
this.refreshDebounced();
}
else {
@@ -251,6 +432,79 @@ export class TimelinePane extends ViewPane {
}
}
private mergeItems(source: string, items: TreeElement[] | undefined, options: TimelineOptions): boolean {
if (items?.length === undefined || items.length === 0) {
return false;
}
if (options.before) {
const ids = new Set();
const timestamps = new Set();
for (const item of items) {
if (item.id === undefined) {
timestamps.add(item.timestamp);
}
else {
ids.add(item.id);
}
}
// Remove any duplicate items
// I don't think we need to check all the items, just the most recent page
let i = Math.min(SubsequentPageSize, this._items.length);
let item;
while (i--) {
item = this._items[i].element;
if (
(item.id === undefined && ids.has(item.id)) ||
(item.timestamp === undefined && timestamps.has(item.timestamp))
) {
this._items.splice(i, 1);
}
}
this._items.splice(0, 0, ...items.map(item => ({ element: item })));
} else {
this._items.push(...items.map(item => ({ element: item })));
}
this.sortItems();
return true;
}
private replaceItems(source: string, items?: TreeElement[]): boolean {
if (items?.length) {
this._items.splice(
0, this._items.length,
...this._items.filter(item => item.element.source !== source),
...items.map(item => ({ element: item }))
);
this.sortItems();
return true;
}
if (this._items.length && this._items.some(item => item.element.source === source)) {
this._items = this._items.filter(item => item.element.source !== source);
return true;
}
return false;
}
private sortItems() {
this._items.sort(
(a, b) =>
(b.element.timestamp - a.element.timestamp) ||
(a.element.source === undefined
? b.element.source === undefined ? 0 : 1
: b.element.source === undefined ? -1 : b.element.source.localeCompare(a.element.source, undefined, { numeric: true, sensitivity: 'base' }))
);
}
private refresh() {
if (this._loadingMessageTimer) {
clearTimeout(this._loadingMessageTimer);
@@ -263,7 +517,7 @@ export class TimelinePane extends ViewPane {
this.message = undefined;
}
this._tree.setChildren(null, this._items.map(item => ({ element: item })));
this._tree.setChildren(null, this._items);
}
@debounce(500)
@@ -282,6 +536,7 @@ export class TimelinePane extends ViewPane {
this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables);
this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables);
this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables);
this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables);
this.onActiveEditorChanged();
@@ -329,9 +584,24 @@ export class TimelinePane extends ViewPane {
}
const selection = this._tree.getSelection();
const command = selection.length === 1 ? selection[0]?.command : undefined;
if (command) {
this.commandService.executeCommand(command.id, ...(command.arguments || []));
const item = selection.length === 1 ? selection[0] : undefined;
// eslint-disable-next-line eqeqeq
if (item == null) {
return;
}
if (isTimelineItem(item)) {
if (item.command) {
this.commandService.executeCommand(item.command.id, ...(item.command.arguments || []));
}
}
else if (isLoadMoreCommandItem(item)) {
// TODO: Change this, but right now this is the pending signal
if (item.themeIcon !== undefined) {
return;
}
this.loadTimeline(false);
}
})
);
@@ -417,6 +687,11 @@ export class TimelineIdentityProvider implements IIdentityProvider<TreeElement>
class TimelineActionRunner extends ActionRunner {
runAction(action: IAction, { uri, item }: TimelineActionContext): Promise<any> {
if (!isTimelineItem(item)) {
// TODO
return action.run();
}
return action.run(...[
{
$mid: 11,
@@ -499,7 +774,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
matches: createMatches(node.filterData)
});
template.timestamp.textContent = fromNow(item.timestamp);
template.timestamp.textContent = isTimelineItem(item) ? fromNow(item.timestamp) : '';
template.actionBar.context = { uri: this._uri, item: item } as TimelineActionContext;
template.actionBar.actionRunner = new TimelineActionRunner();

View File

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

View File

@@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
// import { basename } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
export class TimelineService implements ITimelineService {
_serviceBrand: undefined;
@@ -20,6 +20,9 @@ export class TimelineService implements ITimelineService {
private readonly _onDidChangeTimeline = new Emitter<TimelineChangeEvent>();
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
private readonly _onDidReset = new Emitter<void>();
readonly onDidReset: Event<void> = this._onDidReset.event;
private readonly _providers = new Map<string, TimelineProvider>();
private readonly _providerSubscriptions = new Map<string, IDisposable>();
@@ -81,7 +84,7 @@ export class TimelineService implements ITimelineService {
return [...this._providers.keys()];
}
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) {
getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) {
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`);
const provider = this._providers.get(id);
@@ -98,7 +101,7 @@ export class TimelineService implements ITimelineService {
}
return {
result: provider.provideTimeline(uri, cursor, tokenSource.token, options)
result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions)
.then(result => {
if (result === undefined) {
return undefined;
@@ -109,6 +112,7 @@ export class TimelineService implements ITimelineService {
return result;
}),
options: options,
source: provider.id,
tokenSource: tokenSource,
uri: uri
@@ -156,4 +160,12 @@ export class TimelineService implements ITimelineService {
this._providerSubscriptions.delete(id);
this._onDidChangeProviders.fire({ removed: [id] });
}
// refresh(fetch?: 'all' | 'more') {
// this._onDidChangeTimeline.fire({ fetch: fetch });
// }
reset() {
this._onDidReset.fire();
}
}

View File

@@ -9,14 +9,16 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IUserDataSyncEnablementService, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync';
import { IProductService } from 'vs/platform/product/common/productService';
class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution {
constructor(
@IProductService productService: IProductService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
) {
if (getUserDataSyncStore(configurationService)) {
if (getUserDataSyncStore(productService, configurationService)) {
if (!configurationService.getValue('sync.enableSettings')) {
userDataSyncEnablementService.setResourceEnablement('settings', false);
}

View File

@@ -47,6 +47,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { fromNow } from 'vs/base/common/date';
import { IProductService } from 'vs/platform/product/common/productService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IOpenerService } from 'vs/platform/opener/common/opener';
const enum AuthStatus {
Initializing = 'Initializing',
@@ -135,9 +137,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService,
@IStorageService private readonly storageService: IStorageService,
@IOpenerService private readonly openerService: IOpenerService,
) {
super();
this.userDataSyncStore = getUserDataSyncStore(configurationService);
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService);
@@ -235,7 +239,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
this.updateBadge();
this.registerSyncStatusAction();
}
private async onDidChangeSessions(providerId: string): Promise<void> {
@@ -396,7 +399,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
severity: Severity.Info,
message: localize('turned off', "Sync was turned off from another device."),
actions: {
primary: [new Action('turn on sync', localize('Turn sync back on', "Turn Sync Back On"), undefined, true, () => this.turnOn())]
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())]
}
});
return;
@@ -484,6 +487,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
private async turnOn(): Promise<void> {
if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) {
const result = await this.dialogService.show(
Severity.Info,
localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
[
localize('open doc', "Open Documentation"),
localize('confirm', "Continue"),
localize('cancel', "Cancel"),
],
{
cancelId: 2
}
);
switch (result.choice) {
case 0: this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=827846')); return;
case 2: return;
}
}
return new Promise((c, e) => {
const disposables: DisposableStore = new DisposableStore();
const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
@@ -495,7 +516,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
quickPick.customLabel = localize('turn on', "Turn on");
} else {
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName);
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
}
quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
@@ -523,6 +544,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
await this.handleFirstTimeSync();
this.userDataSyncEnablementService.setEnablement(true);
this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on."));
this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL);
}
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
@@ -746,7 +768,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private registerSignInAction(): void {
const that = this;
registerAction2(class StopSyncAction extends Action2 {
this._register(registerAction2(class StopSyncAction extends Action2 {
constructor() {
super({
id: signInCommand.id,
@@ -766,7 +788,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
that.notificationService.error(e);
}
}
});
}));
}
private registerShowSettingsConflictsAction(): void {
@@ -824,17 +846,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
private readonly _syncStatusActionDisposable = this._register(new MutableDisposable());
private registerSyncStatusAction(): void {
const that = this;
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
this._syncStatusActionDisposable.value = registerAction2(class SyncStatusAction extends Action2 {
this._register(registerAction2(class SyncStatusAction extends Action2 {
constructor() {
super({
id: 'workbench.userData.actions.syncStatus',
get title() {
return getIdentityTitle(localize('sync is on', "Sync is on"), that.activeAccount);
},
title: localize('sync is on', "Sync is on"),
menu: [
{
id: MenuId.GlobalActivity,
@@ -890,12 +909,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
quickPick.show();
});
}
});
}));
}
private registerTurnOffSyncAction(): void {
const that = this;
registerAction2(class StopSyncAction extends Action2 {
this._register(registerAction2(class StopSyncAction extends Action2 {
constructor() {
super({
id: stopSyncCommand.id,
@@ -915,12 +934,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
}
});
}));
}
private registerConfigureSyncAction(): void {
const that = this;
registerAction2(class ShowSyncActivityAction extends Action2 {
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
constructor() {
super({
id: configureSyncCommand.id,
@@ -932,12 +951,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
});
}
run(): any { return that.configureSyncOptions(); }
});
}));
}
private registerShowActivityAction(): void {
const that = this;
registerAction2(class ShowSyncActivityAction extends Action2 {
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
constructor() {
super({
id: showSyncActivityCommand.id,
@@ -949,11 +968,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
});
}
run(): any { return that.showSyncActivity(); }
});
}));
}
private registerShowSettingsAction(): void {
registerAction2(class ShowSyncSettingsAction extends Action2 {
this._register(registerAction2(class ShowSyncSettingsAction extends Action2 {
constructor() {
super({
id: showSyncSettingsCommand.id,
@@ -965,9 +984,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
});
}
run(accessor: ServicesAccessor): any {
accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' });
accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' });
}
});
}));
}
}