mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 5e80bf449c995aa32a59254c0ff845d37da11b70 (#9317)
This commit is contained in:
@@ -154,6 +154,7 @@
|
||||
|
||||
.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon {
|
||||
font-size: 12px;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.extensions-viewlet.narrow > .extensions .extension > .icon-container,
|
||||
|
||||
@@ -80,7 +80,7 @@ export class SettingsEditor2 extends BaseEditor {
|
||||
private static CONFIG_SCHEMA_UPDATE_DELAYER = 500;
|
||||
|
||||
private static readonly SUGGESTIONS: string[] = [
|
||||
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}`
|
||||
`@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}`
|
||||
];
|
||||
|
||||
private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean {
|
||||
|
||||
@@ -556,8 +556,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
|
||||
order: 1
|
||||
});
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Next Search Result', category);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Previous Search Result', category);
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category);
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, {
|
||||
|
||||
@@ -488,12 +488,19 @@ export class FocusNextSearchResultAction extends Action {
|
||||
static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IViewsService private readonly viewsService: IViewsService
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const input = this.editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
|
||||
return (this.editorService.activeControl as SearchEditor).focusNextResult();
|
||||
}
|
||||
|
||||
return openSearchView(this.viewsService).then(searchView => {
|
||||
if (searchView) {
|
||||
searchView.selectNextMatch();
|
||||
@@ -507,12 +514,19 @@ export class FocusPreviousSearchResultAction extends Action {
|
||||
static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IViewsService private readonly viewsService: IViewsService
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
const input = this.editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
// cast as we cannot import SearchEditor as a value b/c cyclic dependency.
|
||||
return (this.editorService.activeControl as SearchEditor).focusPreviousResult();
|
||||
}
|
||||
|
||||
return openSearchView(this.viewsService).then(searchView => {
|
||||
if (searchView) {
|
||||
searchView.selectPreviousMatch();
|
||||
|
||||
@@ -181,25 +181,26 @@ export class SearchView extends ViewPane {
|
||||
|
||||
this.container = dom.$('.search-view');
|
||||
|
||||
// globals
|
||||
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService);
|
||||
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService);
|
||||
this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService);
|
||||
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService);
|
||||
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
|
||||
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
|
||||
|
||||
// scoped
|
||||
this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container));
|
||||
const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService);
|
||||
viewletFocused.set(true);
|
||||
|
||||
this.instantiationService = this.instantiationService.createChild(
|
||||
new ServiceCollection([IContextKeyService, this.contextKeyService]));
|
||||
|
||||
this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService);
|
||||
Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true);
|
||||
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
|
||||
this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService);
|
||||
this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService);
|
||||
this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(contextKeyService);
|
||||
this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService);
|
||||
this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService);
|
||||
this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService);
|
||||
this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService);
|
||||
|
||||
this.instantiationService = this.instantiationService.createChild(
|
||||
new ServiceCollection([IContextKeyService, this.contextKeyService]));
|
||||
|
||||
this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('search.sortOrder')) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord
|
||||
export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
|
||||
export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
|
||||
export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
|
||||
export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
|
||||
|
||||
export const InSearchEditor = new RawContextKey<boolean>('inSearchEditor', false);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE
|
||||
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
|
||||
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
|
||||
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
|
||||
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
|
||||
import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -146,6 +146,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L }
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
|
||||
handler: selectAllSearchEditorMatchesCommand
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(
|
||||
SearchEditorConstants.RerunSearchEditorSearchCommandId,
|
||||
(accessor: ServicesAccessor) => {
|
||||
|
||||
@@ -47,6 +47,8 @@ import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/;
|
||||
const FILE_LINE_REGEX = /^(\S.*):$/;
|
||||
@@ -299,6 +301,42 @@ export class SearchEditor extends BaseTextEditor {
|
||||
return this.configurationService.getValue<ISearchConfigurationProperties>('search');
|
||||
}
|
||||
|
||||
private iterateThroughMatches(reverse: boolean) {
|
||||
const model = this.searchResultEditor.getModel();
|
||||
if (!model) { return; }
|
||||
|
||||
const lastLine = model.getLineCount() ?? 1;
|
||||
const lastColumn = model.getLineLength(lastLine);
|
||||
|
||||
const fallbackStart = reverse ? new Position(lastLine, lastColumn) : new Position(1, 1);
|
||||
|
||||
const currentPosition = this.searchResultEditor.getSelection()?.getStartPosition() ?? fallbackStart;
|
||||
|
||||
const matchRanges = this.getInput()?.getMatchRanges();
|
||||
if (!matchRanges) { return; }
|
||||
|
||||
const matchRange = (reverse ? findPrevRange : findNextRange)(matchRanges, currentPosition);
|
||||
|
||||
this.searchResultEditor.setSelection(matchRange);
|
||||
this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber);
|
||||
this.searchResultEditor.focus();
|
||||
}
|
||||
|
||||
focusNextResult() {
|
||||
this.iterateThroughMatches(false);
|
||||
}
|
||||
|
||||
focusPreviousResult() {
|
||||
this.iterateThroughMatches(true);
|
||||
}
|
||||
|
||||
focusAllResults() {
|
||||
this.searchResultEditor
|
||||
.setSelections((this.getInput()?.getMatchRanges() ?? []).map(
|
||||
range => new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)));
|
||||
this.searchResultEditor.focus();
|
||||
}
|
||||
|
||||
async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) {
|
||||
const options = { resetCursor: true, delay: 0, ..._options };
|
||||
|
||||
@@ -307,7 +345,7 @@ export class SearchEditor extends BaseTextEditor {
|
||||
await this.doRunSearch();
|
||||
this.toggleRunAgainMessage(false);
|
||||
if (options.resetCursor) {
|
||||
this.searchResultEditor.setSelection(new Range(1, 1, 1, 1));
|
||||
this.searchResultEditor.setPosition(new Position(1, 1));
|
||||
this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 });
|
||||
}
|
||||
}, options.delay);
|
||||
@@ -523,3 +561,24 @@ registerThemingParticipant((theme, collector) => {
|
||||
});
|
||||
|
||||
export const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border."));
|
||||
|
||||
function findNextRange(matchRanges: Range[], currentPosition: Position) {
|
||||
for (const matchRange of matchRanges) {
|
||||
if (Position.isBefore(currentPosition, matchRange.getStartPosition())) {
|
||||
return matchRange;
|
||||
}
|
||||
}
|
||||
return matchRanges[0];
|
||||
}
|
||||
|
||||
function findPrevRange(matchRanges: Range[], currentPosition: Position) {
|
||||
for (let i = matchRanges.length - 1; i >= 0; i--) {
|
||||
const matchRange = matchRanges[i];
|
||||
if (Position.isBefore(matchRange.getStartPosition(), currentPosition)) {
|
||||
{
|
||||
return matchRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchRanges[matchRanges.length - 1];
|
||||
}
|
||||
|
||||
@@ -54,6 +54,14 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor
|
||||
}
|
||||
};
|
||||
|
||||
export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const input = editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
(editorService.activeControl as SearchEditor).focusAllResults();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export class OpenSearchEditorAction extends Action {
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineSe
|
||||
import { TimelinePane } from './timelinePane';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
export class TimelinePaneDescriptor implements IViewDescriptor {
|
||||
@@ -42,13 +44,102 @@ configurationRegistry.registerConfiguration({
|
||||
'timeline.showView': {
|
||||
type: 'boolean',
|
||||
description: localize('timeline.showView', "Experimental: When enabled, shows a Timeline view in the Explorer sidebar."),
|
||||
default: false //product.quality !== 'stable'
|
||||
default: product.quality !== 'stable'
|
||||
},
|
||||
'timeline.excludeSources': {
|
||||
type: 'array',
|
||||
description: localize('timeline.excludeSources', "Experimental: An array of Timeline sources that should be excluded from the Timeline view"),
|
||||
default: null
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
if (product.quality !== 'stable') {
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER);
|
||||
|
||||
namespace TimelineViewRefreshAction {
|
||||
|
||||
export const ID = 'timeline.refresh';
|
||||
export const LABEL = localize('timeline.refreshView', "Refresh");
|
||||
|
||||
export function handler(): ICommandHandler {
|
||||
return (accessor, arg) => {
|
||||
const service = accessor.get(ITimelineService);
|
||||
return service.reset();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
|
||||
|
||||
// namespace TimelineViewRefreshHardAction {
|
||||
|
||||
// export const ID = 'timeline.refreshHard';
|
||||
// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)");
|
||||
|
||||
// export function handler(fetch?: 'all' | 'more'): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh(fetch);
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler());
|
||||
|
||||
// namespace TimelineViewLoadMoreAction {
|
||||
|
||||
// export const ID = 'timeline.loadMore';
|
||||
// export const LABEL = localize('timeline.loadMoreInView', "Load More");
|
||||
|
||||
// export function handler(): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh('more');
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler());
|
||||
|
||||
// namespace TimelineViewLoadAllAction {
|
||||
|
||||
// export const ID = 'timeline.loadAll';
|
||||
// export const LABEL = localize('timeline.loadAllInView', "Load All");
|
||||
|
||||
// export function handler(): ICommandHandler {
|
||||
// return (accessor, arg) => {
|
||||
// const service = accessor.get(ITimelineService);
|
||||
// return service.refresh('all');
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler());
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
command: {
|
||||
id: TimelineViewRefreshAction.ID,
|
||||
title: TimelineViewRefreshAction.LABEL,
|
||||
icon: { id: 'codicon/refresh' }
|
||||
}
|
||||
}));
|
||||
|
||||
// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({
|
||||
// group: 'navigation',
|
||||
// order: 2,
|
||||
// command: {
|
||||
// id: TimelineViewLoadMoreAction.ID,
|
||||
// title: TimelineViewLoadMoreAction.LABEL,
|
||||
// icon: { id: 'codicon/unfold' }
|
||||
// },
|
||||
// alt: {
|
||||
// id: TimelineViewLoadAllAction.ID,
|
||||
// title: TimelineViewLoadAllAction.LABEL,
|
||||
// icon: { id: 'codicon/unfold' }
|
||||
|
||||
// }
|
||||
// }));
|
||||
|
||||
registerSingleton(ITimelineService, TimelineService, true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
@@ -18,9 +19,9 @@ import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/bro
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { SideBySideEditor, toResource } from 'vs/workbench/common/editor';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -28,7 +29,6 @@ import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeS
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -40,13 +40,53 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
// TODO[ECA]: Localize all the strings
|
||||
|
||||
type TreeElement = TimelineItem;
|
||||
const InitialPageSize = 20;
|
||||
const SubsequentPageSize = 40;
|
||||
|
||||
interface CommandItem {
|
||||
handle: 'vscode-command:loadMore';
|
||||
timestamp: number;
|
||||
label: string;
|
||||
themeIcon?: { id: string };
|
||||
description?: string;
|
||||
detail?: string;
|
||||
contextValue?: string;
|
||||
|
||||
// Make things easier for duck typing
|
||||
id: undefined;
|
||||
icon: undefined;
|
||||
iconDark: undefined;
|
||||
source: undefined;
|
||||
}
|
||||
|
||||
type TreeElement = TimelineItem | CommandItem;
|
||||
|
||||
// function isCommandItem(item: TreeElement | undefined): item is CommandItem {
|
||||
// return item?.handle.startsWith('vscode-command:') ?? false;
|
||||
// }
|
||||
|
||||
function isLoadMoreCommandItem(item: TreeElement | undefined): item is CommandItem & {
|
||||
handle: 'vscode-command:loadMore';
|
||||
} {
|
||||
return item?.handle === 'vscode-command:loadMore';
|
||||
}
|
||||
|
||||
function isTimelineItem(item: TreeElement | undefined): item is TimelineItem {
|
||||
return !item?.handle.startsWith('vscode-command:') ?? false;
|
||||
}
|
||||
|
||||
|
||||
interface TimelineActionContext {
|
||||
uri: URI | undefined;
|
||||
item: TreeElement;
|
||||
}
|
||||
|
||||
interface TimelineCursors {
|
||||
startCursors?: { before: any; after?: any };
|
||||
endCursors?: { before: any; after?: any };
|
||||
more: boolean;
|
||||
}
|
||||
|
||||
export class TimelinePane extends ViewPane {
|
||||
static readonly ID = 'timeline';
|
||||
static readonly TITLE = localize('timeline', 'Timeline');
|
||||
@@ -59,8 +99,9 @@ export class TimelinePane extends ViewPane {
|
||||
private _menus: TimelineMenus;
|
||||
private _visibilityDisposables: DisposableStore | undefined;
|
||||
|
||||
// private _excludedSources: Set<string> | undefined;
|
||||
private _items: TimelineItem[] = [];
|
||||
private _excludedSources: Set<string>;
|
||||
private _cursorsByProvider: Map<string, TimelineCursors> = new Map();
|
||||
private _items: { element: TreeElement }[] = [];
|
||||
private _loadingMessageTimer: any | undefined;
|
||||
private _pendingRequests = new Map<string, TimelineRequest>();
|
||||
private _uri: URI | undefined;
|
||||
@@ -87,6 +128,18 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('view', TimelinePane.ID);
|
||||
|
||||
this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources'));
|
||||
configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this);
|
||||
}
|
||||
|
||||
private onConfigurationChanged(e: IConfigurationChangeEvent) {
|
||||
if (!e.affectsConfiguration('timeline.excludeSources')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._excludedSources = new Set(this.configurationService.getValue('timeline.excludeSources'));
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private onActiveEditorChanged() {
|
||||
@@ -105,7 +158,7 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
this._uri = uri;
|
||||
this._treeRenderer?.setUri(uri);
|
||||
this.loadTimeline();
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private onProvidersChanged(e: TimelineProvidersChangeEvent) {
|
||||
@@ -116,16 +169,20 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
if (e.added) {
|
||||
this.loadTimeline(e.added);
|
||||
this.loadTimeline(true, e.added);
|
||||
}
|
||||
}
|
||||
|
||||
private onTimelineChanged(e: TimelineChangeEvent) {
|
||||
if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.loadTimeline([e.id]);
|
||||
if (e?.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) {
|
||||
this.loadTimeline(e.reset ?? false, e?.id === undefined ? undefined : [e.id], { before: !e.reset });
|
||||
}
|
||||
}
|
||||
|
||||
private onReset() {
|
||||
this.loadTimeline(true);
|
||||
}
|
||||
|
||||
private _message: string | undefined;
|
||||
get message(): string | undefined {
|
||||
return this._message;
|
||||
@@ -160,22 +217,27 @@ export class TimelinePane extends ViewPane {
|
||||
DOM.clearNode(this._messageElement);
|
||||
}
|
||||
|
||||
private async loadTimeline(sources?: string[]) {
|
||||
private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) {
|
||||
const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize;
|
||||
|
||||
// If we have no source, we are reseting all sources, so cancel everything in flight and reset caches
|
||||
if (sources === undefined) {
|
||||
this._items.length = 0;
|
||||
if (reset) {
|
||||
this._items.length = 0;
|
||||
this._cursorsByProvider.clear();
|
||||
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
this._loadingMessageTimer = undefined;
|
||||
}
|
||||
|
||||
for (const { tokenSource } of this._pendingRequests.values()) {
|
||||
tokenSource.dispose(true);
|
||||
}
|
||||
|
||||
this._pendingRequests.clear();
|
||||
}
|
||||
|
||||
for (const { tokenSource } of this._pendingRequests.values()) {
|
||||
tokenSource.dispose(true);
|
||||
}
|
||||
|
||||
this._pendingRequests.clear();
|
||||
|
||||
// TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way?
|
||||
if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) {
|
||||
this.message = 'The active editor cannot provide timeline information.';
|
||||
@@ -184,7 +246,7 @@ export class TimelinePane extends ViewPane {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._uri !== undefined) {
|
||||
if (reset && this._uri !== undefined) {
|
||||
this._loadingMessageTimer = setTimeout((uri: URI) => {
|
||||
if (uri !== this._uri) {
|
||||
return;
|
||||
@@ -200,50 +262,169 @@ export class TimelinePane extends ViewPane {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const source of sources ?? this.timelineService.getSources()) {
|
||||
const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s));
|
||||
if (filteredSources.length === 0) {
|
||||
if (reset) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let lastIndex = this._items.length - 1;
|
||||
let lastItem = this._items[lastIndex]?.element;
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
lastItem.themeIcon = { id: 'sync~spin' };
|
||||
// this._items.splice(lastIndex, 1);
|
||||
lastIndex--;
|
||||
|
||||
if (!reset && !options.before) {
|
||||
lastItem = this._items[lastIndex]?.element;
|
||||
const selection = [lastItem];
|
||||
this._tree.setSelection(selection);
|
||||
this._tree.setFocus(selection);
|
||||
}
|
||||
}
|
||||
|
||||
for (const source of filteredSources) {
|
||||
let request = this._pendingRequests.get(source);
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!;
|
||||
const cursors = this._cursorsByProvider.get(source);
|
||||
if (!reset) {
|
||||
// TODO: Handle pending request
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
if (cursors?.more === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const reusingToken = request?.tokenSource !== undefined;
|
||||
request = this.timelineService.getTimeline(
|
||||
source, this._uri,
|
||||
{
|
||||
cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after,
|
||||
...options,
|
||||
limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize
|
||||
},
|
||||
request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
if (!reusingToken) {
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
}
|
||||
} else {
|
||||
request?.tokenSource.dispose(true);
|
||||
|
||||
request = this.timelineService.getTimeline(
|
||||
source, this._uri,
|
||||
{
|
||||
...options,
|
||||
limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize
|
||||
},
|
||||
new CancellationTokenSource(), { cacheResults: true }
|
||||
)!;
|
||||
|
||||
this._pendingRequests.set(source, request);
|
||||
request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source));
|
||||
}
|
||||
|
||||
this.handleRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleRequest(request: TimelineRequest) {
|
||||
let items;
|
||||
let timeline: Timeline | undefined;
|
||||
try {
|
||||
items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? []));
|
||||
timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result);
|
||||
}
|
||||
finally {
|
||||
this._pendingRequests.delete(request.source);
|
||||
}
|
||||
catch { }
|
||||
|
||||
this._pendingRequests.delete(request.source);
|
||||
if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) {
|
||||
if (
|
||||
timeline === undefined ||
|
||||
request.tokenSource.token.isCancellationRequested ||
|
||||
request.uri !== this._uri
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.replaceItems(request.source, items);
|
||||
}
|
||||
let items: TreeElement[];
|
||||
|
||||
private replaceItems(source: string, items?: TimelineItem[]) {
|
||||
const hasItems = this._items.length !== 0;
|
||||
const source = request.source;
|
||||
|
||||
if (items?.length) {
|
||||
this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items);
|
||||
this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
if (timeline !== undefined) {
|
||||
if (timeline.paging !== undefined) {
|
||||
let cursors = this._cursorsByProvider.get(timeline.source ?? source);
|
||||
if (cursors === undefined) {
|
||||
cursors = { startCursors: timeline.paging.cursors, more: timeline.paging.more ?? false };
|
||||
this._cursorsByProvider.set(timeline.source, cursors);
|
||||
} else {
|
||||
if (request.options.before) {
|
||||
if (cursors.endCursors === undefined) {
|
||||
cursors.endCursors = cursors.startCursors;
|
||||
}
|
||||
cursors.startCursors = timeline.paging.cursors;
|
||||
}
|
||||
else {
|
||||
if (cursors.startCursors === undefined) {
|
||||
cursors.startCursors = timeline.paging.cursors;
|
||||
}
|
||||
cursors.endCursors = timeline.paging.cursors;
|
||||
}
|
||||
cursors.more = timeline.paging.more ?? true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._cursorsByProvider.delete(source);
|
||||
}
|
||||
else if (this._items.length && this._items.some(i => i.source === source)) {
|
||||
this._items = this._items.filter(i => i.source !== source);
|
||||
items = (timeline.items as TreeElement[]) ?? [];
|
||||
|
||||
const alreadyHadItems = this._items.length !== 0;
|
||||
|
||||
let changed;
|
||||
if (request.options.cursor) {
|
||||
changed = this.mergeItems(request.source, items, request.options);
|
||||
} else {
|
||||
changed = this.replaceItems(request.source, items);
|
||||
}
|
||||
else {
|
||||
|
||||
if (!changed) {
|
||||
// If there are no items at all and no pending requests, make sure to refresh (to show the no timeline info message)
|
||||
if (this._items.length === 0 && this._pendingRequests.size === 0) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._pendingRequests.size === 0 && this._items.length !== 0) {
|
||||
const lastIndex = this._items.length - 1;
|
||||
const lastItem = this._items[lastIndex]?.element;
|
||||
|
||||
if (timeline.paging?.more || Iterator.some(this._cursorsByProvider.values(), cursors => cursors.more)) {
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
lastItem.themeIcon = undefined;
|
||||
}
|
||||
else {
|
||||
this._items.push({
|
||||
element: {
|
||||
handle: 'vscode-command:loadMore',
|
||||
label: 'Load more',
|
||||
timestamp: 0
|
||||
} as CommandItem
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isLoadMoreCommandItem(lastItem)) {
|
||||
this._items.splice(lastIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have items already and there are other pending requests, debounce for a bit to wait for other requests
|
||||
if (hasItems && this._pendingRequests.size !== 0) {
|
||||
if (alreadyHadItems && this._pendingRequests.size !== 0) {
|
||||
this.refreshDebounced();
|
||||
}
|
||||
else {
|
||||
@@ -251,6 +432,79 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
private mergeItems(source: string, items: TreeElement[] | undefined, options: TimelineOptions): boolean {
|
||||
if (items?.length === undefined || items.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.before) {
|
||||
const ids = new Set();
|
||||
const timestamps = new Set();
|
||||
|
||||
for (const item of items) {
|
||||
if (item.id === undefined) {
|
||||
timestamps.add(item.timestamp);
|
||||
}
|
||||
else {
|
||||
ids.add(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any duplicate items
|
||||
// I don't think we need to check all the items, just the most recent page
|
||||
let i = Math.min(SubsequentPageSize, this._items.length);
|
||||
let item;
|
||||
while (i--) {
|
||||
item = this._items[i].element;
|
||||
if (
|
||||
(item.id === undefined && ids.has(item.id)) ||
|
||||
(item.timestamp === undefined && timestamps.has(item.timestamp))
|
||||
) {
|
||||
this._items.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._items.splice(0, 0, ...items.map(item => ({ element: item })));
|
||||
} else {
|
||||
this._items.push(...items.map(item => ({ element: item })));
|
||||
}
|
||||
|
||||
this.sortItems();
|
||||
return true;
|
||||
}
|
||||
|
||||
private replaceItems(source: string, items?: TreeElement[]): boolean {
|
||||
if (items?.length) {
|
||||
this._items.splice(
|
||||
0, this._items.length,
|
||||
...this._items.filter(item => item.element.source !== source),
|
||||
...items.map(item => ({ element: item }))
|
||||
);
|
||||
this.sortItems();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._items.length && this._items.some(item => item.element.source === source)) {
|
||||
this._items = this._items.filter(item => item.element.source !== source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sortItems() {
|
||||
this._items.sort(
|
||||
(a, b) =>
|
||||
(b.element.timestamp - a.element.timestamp) ||
|
||||
(a.element.source === undefined
|
||||
? b.element.source === undefined ? 0 : 1
|
||||
: b.element.source === undefined ? -1 : b.element.source.localeCompare(a.element.source, undefined, { numeric: true, sensitivity: 'base' }))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
if (this._loadingMessageTimer) {
|
||||
clearTimeout(this._loadingMessageTimer);
|
||||
@@ -263,7 +517,7 @@ export class TimelinePane extends ViewPane {
|
||||
this.message = undefined;
|
||||
}
|
||||
|
||||
this._tree.setChildren(null, this._items.map(item => ({ element: item })));
|
||||
this._tree.setChildren(null, this._items);
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
@@ -282,6 +536,7 @@ export class TimelinePane extends ViewPane {
|
||||
|
||||
this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables);
|
||||
this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables);
|
||||
this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables);
|
||||
this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables);
|
||||
|
||||
this.onActiveEditorChanged();
|
||||
@@ -329,9 +584,24 @@ export class TimelinePane extends ViewPane {
|
||||
}
|
||||
|
||||
const selection = this._tree.getSelection();
|
||||
const command = selection.length === 1 ? selection[0]?.command : undefined;
|
||||
if (command) {
|
||||
this.commandService.executeCommand(command.id, ...(command.arguments || []));
|
||||
const item = selection.length === 1 ? selection[0] : undefined;
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTimelineItem(item)) {
|
||||
if (item.command) {
|
||||
this.commandService.executeCommand(item.command.id, ...(item.command.arguments || []));
|
||||
}
|
||||
}
|
||||
else if (isLoadMoreCommandItem(item)) {
|
||||
// TODO: Change this, but right now this is the pending signal
|
||||
if (item.themeIcon !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadTimeline(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -417,6 +687,11 @@ export class TimelineIdentityProvider implements IIdentityProvider<TreeElement>
|
||||
class TimelineActionRunner extends ActionRunner {
|
||||
|
||||
runAction(action: IAction, { uri, item }: TimelineActionContext): Promise<any> {
|
||||
if (!isTimelineItem(item)) {
|
||||
// TODO
|
||||
return action.run();
|
||||
}
|
||||
|
||||
return action.run(...[
|
||||
{
|
||||
$mid: 11,
|
||||
@@ -499,7 +774,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
|
||||
matches: createMatches(node.filterData)
|
||||
});
|
||||
|
||||
template.timestamp.textContent = fromNow(item.timestamp);
|
||||
template.timestamp.textContent = isTimelineItem(item) ? fromNow(item.timestamp) : '';
|
||||
|
||||
template.actionBar.context = { uri: this._uri, item: item } as TimelineActionContext;
|
||||
template.actionBar.actionRunner = new TimelineActionRunner();
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface TimelineItem {
|
||||
handle: string;
|
||||
source: string;
|
||||
|
||||
id?: string;
|
||||
timestamp: number;
|
||||
label: string;
|
||||
icon?: URI,
|
||||
@@ -31,28 +32,34 @@ export interface TimelineItem {
|
||||
}
|
||||
|
||||
export interface TimelineChangeEvent {
|
||||
id: string;
|
||||
id?: string;
|
||||
uri?: URI;
|
||||
reset?: boolean
|
||||
}
|
||||
|
||||
export interface TimelineCursor {
|
||||
cursor?: any;
|
||||
export interface TimelineOptions {
|
||||
cursor?: string;
|
||||
before?: boolean;
|
||||
limit?: number;
|
||||
limit?: number | string;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
source: string;
|
||||
items: TimelineItem[];
|
||||
|
||||
cursor?: any;
|
||||
more?: boolean;
|
||||
paging?: {
|
||||
cursors: {
|
||||
before: string;
|
||||
after?: string
|
||||
};
|
||||
more?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
|
||||
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export interface TimelineProviderDescriptor {
|
||||
@@ -68,6 +75,7 @@ export interface TimelineProvidersChangeEvent {
|
||||
|
||||
export interface TimelineRequest {
|
||||
readonly result: Promise<Timeline | undefined>;
|
||||
readonly options: TimelineOptions;
|
||||
readonly source: string;
|
||||
readonly tokenSource: CancellationTokenSource;
|
||||
readonly uri: URI;
|
||||
@@ -78,13 +86,17 @@ export interface ITimelineService {
|
||||
|
||||
onDidChangeProviders: Event<TimelineProvidersChangeEvent>;
|
||||
onDidChangeTimeline: Event<TimelineChangeEvent>;
|
||||
onDidReset: Event<void>;
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): IDisposable;
|
||||
unregisterTimelineProvider(id: string): void;
|
||||
|
||||
getSources(): string[];
|
||||
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined;
|
||||
getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }): TimelineRequest | undefined;
|
||||
|
||||
// refresh(fetch?: 'all' | 'more'): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
const TIMELINE_SERVICE_ID = 'timeline';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
// import { basename } from 'vs/base/common/path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
|
||||
import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline';
|
||||
|
||||
export class TimelineService implements ITimelineService {
|
||||
_serviceBrand: undefined;
|
||||
@@ -20,6 +20,9 @@ export class TimelineService implements ITimelineService {
|
||||
private readonly _onDidChangeTimeline = new Emitter<TimelineChangeEvent>();
|
||||
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
|
||||
|
||||
private readonly _onDidReset = new Emitter<void>();
|
||||
readonly onDidReset: Event<void> = this._onDidReset.event;
|
||||
|
||||
private readonly _providers = new Map<string, TimelineProvider>();
|
||||
private readonly _providerSubscriptions = new Map<string, IDisposable>();
|
||||
|
||||
@@ -81,7 +84,7 @@ export class TimelineService implements ITimelineService {
|
||||
return [...this._providers.keys()];
|
||||
}
|
||||
|
||||
getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) {
|
||||
getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) {
|
||||
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`);
|
||||
|
||||
const provider = this._providers.get(id);
|
||||
@@ -98,7 +101,7 @@ export class TimelineService implements ITimelineService {
|
||||
}
|
||||
|
||||
return {
|
||||
result: provider.provideTimeline(uri, cursor, tokenSource.token, options)
|
||||
result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions)
|
||||
.then(result => {
|
||||
if (result === undefined) {
|
||||
return undefined;
|
||||
@@ -109,6 +112,7 @@ export class TimelineService implements ITimelineService {
|
||||
|
||||
return result;
|
||||
}),
|
||||
options: options,
|
||||
source: provider.id,
|
||||
tokenSource: tokenSource,
|
||||
uri: uri
|
||||
@@ -156,4 +160,12 @@ export class TimelineService implements ITimelineService {
|
||||
this._providerSubscriptions.delete(id);
|
||||
this._onDidChangeProviders.fire({ removed: [id] });
|
||||
}
|
||||
|
||||
// refresh(fetch?: 'all' | 'more') {
|
||||
// this._onDidChangeTimeline.fire({ fetch: fetch });
|
||||
// }
|
||||
|
||||
reset() {
|
||||
this._onDidReset.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,16 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUserDataSyncEnablementService, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
) {
|
||||
if (getUserDataSyncStore(configurationService)) {
|
||||
if (getUserDataSyncStore(productService, configurationService)) {
|
||||
if (!configurationService.getValue('sync.enableSettings')) {
|
||||
userDataSyncEnablementService.setResourceEnablement('settings', false);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
const enum AuthStatus {
|
||||
Initializing = 'Initializing',
|
||||
@@ -135,9 +137,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStore = getUserDataSyncStore(configurationService);
|
||||
this.userDataSyncStore = getUserDataSyncStore(productService, configurationService);
|
||||
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
|
||||
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
|
||||
this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService);
|
||||
@@ -235,7 +239,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
this.updateBadge();
|
||||
this.registerSyncStatusAction();
|
||||
}
|
||||
|
||||
private async onDidChangeSessions(providerId: string): Promise<void> {
|
||||
@@ -396,7 +399,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
severity: Severity.Info,
|
||||
message: localize('turned off', "Sync was turned off from another device."),
|
||||
actions: {
|
||||
primary: [new Action('turn on sync', localize('Turn sync back on', "Turn Sync Back On"), undefined, true, () => this.turnOn())]
|
||||
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())]
|
||||
}
|
||||
});
|
||||
return;
|
||||
@@ -484,6 +487,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async turnOn(): Promise<void> {
|
||||
if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) {
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
|
||||
[
|
||||
localize('open doc', "Open Documentation"),
|
||||
localize('confirm', "Continue"),
|
||||
localize('cancel', "Cancel"),
|
||||
],
|
||||
{
|
||||
cancelId: 2
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0: this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=827846')); return;
|
||||
case 2: return;
|
||||
}
|
||||
}
|
||||
return new Promise((c, e) => {
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
|
||||
@@ -495,7 +516,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
quickPick.customLabel = localize('turn on', "Turn on");
|
||||
} else {
|
||||
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
|
||||
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
|
||||
}
|
||||
quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
|
||||
@@ -523,6 +544,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
await this.handleFirstTimeSync();
|
||||
this.userDataSyncEnablementService.setEnablement(true);
|
||||
this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on."));
|
||||
this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
|
||||
@@ -746,7 +768,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private registerSignInAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class StopSyncAction extends Action2 {
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: signInCommand.id,
|
||||
@@ -766,7 +788,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
that.notificationService.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowSettingsConflictsAction(): void {
|
||||
@@ -824,17 +846,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
}
|
||||
|
||||
private readonly _syncStatusActionDisposable = this._register(new MutableDisposable());
|
||||
private registerSyncStatusAction(): void {
|
||||
const that = this;
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
|
||||
this._syncStatusActionDisposable.value = registerAction2(class SyncStatusAction extends Action2 {
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.userData.actions.syncStatus',
|
||||
get title() {
|
||||
return getIdentityTitle(localize('sync is on', "Sync is on"), that.activeAccount);
|
||||
},
|
||||
title: localize('sync is on', "Sync is on"),
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.GlobalActivity,
|
||||
@@ -890,12 +909,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerTurnOffSyncAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class StopSyncAction extends Action2 {
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: stopSyncCommand.id,
|
||||
@@ -915,12 +934,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerConfigureSyncAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: configureSyncCommand.id,
|
||||
@@ -932,12 +951,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(): any { return that.configureSyncOptions(); }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowActivityAction(): void {
|
||||
const that = this;
|
||||
registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: showSyncActivityCommand.id,
|
||||
@@ -949,11 +968,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(): any { return that.showSyncActivity(); }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowSettingsAction(): void {
|
||||
registerAction2(class ShowSyncSettingsAction extends Action2 {
|
||||
this._register(registerAction2(class ShowSyncSettingsAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: showSyncSettingsCommand.id,
|
||||
@@ -965,9 +984,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): any {
|
||||
accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' });
|
||||
accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' });
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user