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

@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
@extHostNamedCustomer(MainContext.MainThreadTimeline)
export class MainThreadTimeline implements MainThreadTimelineShape {
@@ -39,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
this._timelineService.registerTimelineProvider({
...provider,
onDidChange: onDidChange.event,
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
return proxy.$getTimeline(provider.id, uri, cursor, token, options);
provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) {
return proxy.$getTimeline(provider.id, uri, options, token, internalOptions);
},
dispose() {
emitters.delete(provider.id);

View File

@@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
@@ -1472,7 +1472,7 @@ export interface ExtHostTunnelServiceShape {
}
export interface ExtHostTimelineShape {
$getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
$getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
// --- proxy identifiers

View File

@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import { UriComponents, URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
import { Timeline, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
@@ -16,7 +16,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export interface IExtHostTimeline extends ExtHostTimelineShape {
readonly _serviceBrand: undefined;
$getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
$getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
@@ -50,9 +50,9 @@ export class ExtHostTimeline implements IExtHostTimeline {
});
}
async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
const provider = this._providers.get(id);
return provider?.provideTimeline(URI.revive(uri), cursor, token, options);
return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions);
}
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
@@ -70,15 +70,15 @@ export class ExtHostTimeline implements IExtHostTimeline {
...provider,
scheme: scheme,
onDidChange: undefined,
async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) {
timelineDisposables.clear();
// For now, only allow the caching of a single Uri
if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
if (internalOptions?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
itemsBySourceByUriMap.clear();
}
const result = await provider.provideTimeline(uri, cursor, token);
const result = await provider.provideTimeline(uri, options, token);
// Intentional == we don't know how a provider will respond
// eslint-disable-next-line eqeqeq
if (result == null) {
@@ -86,7 +86,7 @@ export class ExtHostTimeline implements IExtHostTimeline {
}
// TODO: Determine if we should cache dependent on who calls us (internal vs external)
const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false);
const convertItem = convertTimelineItem(uri, internalOptions?.cacheResults ?? false);
return {
...result,
source: provider.id,
@@ -143,6 +143,7 @@ export class ExtHostTimeline implements IExtHostTimeline {
return {
...props,
id: props.id ?? undefined,
handle: handle,
source: source,
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,

View File

@@ -389,7 +389,7 @@ class WebviewDocumentStore {
}
private key(viewType: string, resource: vscode.Uri): string {
return `${viewType}@@@${resource.toString}`;
return `${viewType}@@@${resource}`;
}
}

View File

@@ -21,10 +21,13 @@ import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
const viewCategory = nls.localize('view', "View");
@@ -518,6 +521,92 @@ export class ResetViewLocationsAction extends Action {
registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetViewLocationsAction, ResetViewLocationsAction.ID, ResetViewLocationsAction.LABEL), 'View: Reset View Locations', viewCategory);
// --- Move View with Command
export class MoveFocusedViewAction extends Action {
static readonly ID = 'workbench.action.moveFocusedView';
static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
@IViewsService private viewsService: IViewsService,
@IQuickInputService private quickInputService: IQuickInputService,
@IContextKeyService private contextKeyService: IContextKeyService,
@INotificationService private notificationService: INotificationService,
@IViewletService private viewletService: IViewletService
) {
super(id, label);
}
run(): Promise<void> {
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const focusedView = FocusedViewContext.getValue(this.contextKeyService);
if (focusedView === undefined || focusedView.trim() === '') {
this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."));
return Promise.resolve();
}
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedView);
if (!viewDescriptor || !viewDescriptor.canMoveView) {
this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable {0}.", focusedView));
return Promise.resolve();
}
const quickPick = this.quickInputService.createQuickPick();
quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a destination area for the view...");
quickPick.autoFocusOnList = true;
quickPick.items = [
{
id: 'sidebar',
label: nls.localize('sidebar', "Sidebar")
},
{
id: 'panel',
label: nls.localize('panel', "Panel")
}
];
quickPick.onDidAccept(() => {
const destination = quickPick.selectedItems[0];
if (destination.id === 'panel') {
quickPick.hide();
this.viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel);
this.viewsService.openView(focusedView, true);
return;
} else if (destination.id === 'sidebar') {
quickPick.placeholder = nls.localize('moveFocusedView.selectDestinationContainer', "Select a destination view group...");
quickPick.items = this.viewletService.getViewlets().map(viewlet => {
return {
id: viewlet.id,
label: viewlet.name
};
});
return;
} else if (destination.id) {
quickPick.hide();
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], viewContainerRegistry.get(destination.id)!);
this.viewsService.openView(focusedView, true);
return;
}
quickPick.hide();
});
quickPick.show();
return Promise.resolve();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory);
// --- Resize View

View File

@@ -4,19 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { posix } from 'vs/base/common/path';
import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources';
import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IModeService } from 'vs/editor/common/services/modeService';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations';
import { Schemas } from 'vs/base/common/network';
import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files';
import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
import { ITextModel } from 'vs/editor/common/model';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
@@ -89,7 +87,6 @@ export class ResourceLabels extends Disposable {
@IModelService private readonly modelService: IModelService,
@IDecorationsService private readonly decorationsService: IDecorationsService,
@IThemeService private readonly themeService: IThemeService,
@IFileService private readonly fileService: IFileService,
@ILabelService private readonly labelService: ILabelService,
@ITextFileService private readonly textFileService: ITextFileService
) {
@@ -114,10 +111,6 @@ export class ResourceLabels extends Disposable {
return; // we need the resource to compare
}
if (this.fileService.canHandleResource(e.model.uri) && e.oldModeId === PLAINTEXT_MODE_ID) {
return; // ignore transitions in files from no mode to specific mode because this happens each time a model is created
}
this._widgets.forEach(widget => widget.notifyModelModeChanged(e.model));
}));
@@ -218,11 +211,10 @@ export class ResourceLabel extends ResourceLabels {
@IModelService modelService: IModelService,
@IDecorationsService decorationsService: IDecorationsService,
@IThemeService themeService: IThemeService,
@IFileService fileService: IFileService,
@ILabelService labelService: ILabelService,
@ITextFileService textFileService: ITextFileService
) {
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService, textFileService);
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService);
this._label = this._register(this.create(container, options));
}
@@ -372,8 +364,8 @@ class ResourceLabelWidget extends IconLabel {
let untitledDescription = untitledModel.resource.path;
if (label.name !== untitledDescription) {
label.description = untitledDescription;
} else if (label.description === posix.sep) {
label.description = undefined; // unset showing just "/" for untitled without associated resource
} else {
label.description = undefined;
}
}

View File

@@ -547,6 +547,13 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateFromDragging(container, true);
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data) && data[0].id !== this.activity.id) {
this.updateFromDragging(container, true);
}
}
},
onDragOver: e => {
@@ -575,7 +582,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
},
onDragLeave: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
this.updateFromDragging(container, false);
}
},

View File

@@ -44,7 +44,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR
KeybindingsRegistry.registerKeybindingRule({
id: descriptor.id,
weight: weight,
when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null),
when:
descriptor.keybindingContext && when
? ContextKeyExpr.and(descriptor.keybindingContext, when)
: descriptor.keybindingContext || when || null,
primary: keybindings ? keybindings.primary : 0,
secondary: keybindings?.secondary,
win: keybindings?.win,

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' });
}
});
}));
}
}

View File

@@ -34,7 +34,6 @@ import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/d
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
import { ILogService } from 'vs/platform/log/common/log';
/**
* Stores the selection & view state of an editor and allows to compare it to other selection states.
@@ -112,8 +111,7 @@ export class HistoryService extends Disposable implements IHistoryService {
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@ILogService private readonly logService: ILogService
@IContextKeyService private readonly contextKeyService: IContextKeyService
) {
super();
@@ -277,7 +275,9 @@ export class HistoryService extends Disposable implements IHistoryService {
}
clear(): void {
this.ensureHistoryLoaded();
// History
this.clearRecentlyOpened();
// Navigation (next, previous)
this.navigationStackIndex = -1;
@@ -289,9 +289,6 @@ export class HistoryService extends Disposable implements IHistoryService {
// Closed files
this.recentlyClosedFiles = [];
// History
this.clearRecentlyOpened();
// Context Keys
this.updateContextKeys();
}
@@ -719,8 +716,8 @@ export class HistoryService extends Disposable implements IHistoryService {
private static readonly MAX_HISTORY_ITEMS = 200;
private static readonly HISTORY_STORAGE_KEY = 'history.entries';
private history: Array<IEditorInput | IResourceInput> = [];
private loaded = false;
private history: Array<IEditorInput | IResourceInput> | undefined = undefined;
private readonly resourceFilter = this._register(this.instantiationService.createInstance(
ResourceGlobMatcher,
(root?: URI) => this.getExcludes(root),
@@ -741,11 +738,10 @@ export class HistoryService extends Disposable implements IHistoryService {
return;
}
this.ensureHistoryLoaded();
const historyInput = this.preferResourceInput(input);
// Remove any existing entry and add to the beginning
this.ensureHistoryLoaded(this.history);
this.removeFromHistory(input);
this.history.unshift(historyInput);
@@ -772,7 +768,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private removeExcludedFromHistory(): void {
this.ensureHistoryLoaded();
this.ensureHistoryLoaded(this.history);
this.history = this.history.filter(e => {
const include = this.include(e);
@@ -787,7 +783,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void {
this.ensureHistoryLoaded();
this.ensureHistoryLoaded(this.history);
this.history = this.history.filter(e => {
const matches = this.matches(arg1, e);
@@ -809,17 +805,59 @@ export class HistoryService extends Disposable implements IHistoryService {
}
getHistory(): ReadonlyArray<IEditorInput | IResourceInput> {
this.ensureHistoryLoaded();
this.ensureHistoryLoaded(this.history);
return this.history.slice(0);
}
private ensureHistoryLoaded(): void {
if (!this.loaded) {
this.loadHistory();
private ensureHistoryLoaded(history: Array<IEditorInput | IResourceInput> | undefined): asserts history {
if (!this.history) {
this.history = this.loadHistory();
}
}
private loadHistory(): Array<IEditorInput | IResourceInput> {
let entries: ISerializedEditorHistoryEntry[] = [];
const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
if (entriesRaw) {
entries = coalesce(JSON.parse(entriesRaw));
}
this.loaded = true;
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
return coalesce(entries.map(entry => {
try {
return this.safeLoadHistoryEntry(registry, entry);
} catch (error) {
return undefined; // https://github.com/Microsoft/vscode/issues/60960
}
}));
}
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined {
const serializedEditorHistoryEntry = entry;
// File resource: via URI.revive()
if (serializedEditorHistoryEntry.resourceJSON) {
return { resource: URI.revive(<UriComponents>serializedEditorHistoryEntry.resourceJSON) };
}
// Editor input: via factory
const { editorInputJSON } = serializedEditorHistoryEntry;
if (editorInputJSON?.deserialized) {
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
if (factory) {
const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
if (input) {
this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners);
}
return withNullAsUndefined(input);
}
}
return undefined;
}
private saveState(): void {
@@ -850,58 +888,9 @@ export class HistoryService extends Disposable implements IHistoryService {
return undefined;
}));
this.logService.trace(`[editor history] saving ${entries.length} entries`);
this.storageService.store(HistoryService.HISTORY_STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE);
}
private loadHistory(): void {
let entries: ISerializedEditorHistoryEntry[] = [];
const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
if (entriesRaw) {
entries = coalesce(JSON.parse(entriesRaw));
}
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
this.history = coalesce(entries.map(entry => {
try {
return this.safeLoadHistoryEntry(registry, entry);
} catch (error) {
this.logService.error(`[editor history] error loading one editor history entry: ${error.toString()}`);
return undefined; // https://github.com/Microsoft/vscode/issues/60960
}
}));
this.logService.trace(`[editor history] loading ${this.history.length} entries`);
}
private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined {
const serializedEditorHistoryEntry = entry;
// File resource: via URI.revive()
if (serializedEditorHistoryEntry.resourceJSON) {
return { resource: URI.revive(<UriComponents>serializedEditorHistoryEntry.resourceJSON) };
}
// Editor input: via factory
const { editorInputJSON } = serializedEditorHistoryEntry;
if (editorInputJSON?.deserialized) {
const factory = registry.getEditorInputFactory(editorInputJSON.typeId);
if (factory) {
const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized);
if (input) {
this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners);
}
return withNullAsUndefined(input);
}
}
return undefined;
}
//#endregion
//#region Last Active Workspace/File
@@ -925,8 +914,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
// Multiple folders: find the last active one
const history = this.getHistory();
for (const input of history) {
for (const input of this.getHistory()) {
if (input instanceof EditorInput) {
continue;
}
@@ -954,8 +942,7 @@ export class HistoryService extends Disposable implements IHistoryService {
}
getLastActiveFile(filterByScheme: string): URI | undefined {
const history = this.getHistory();
for (const input of history) {
for (const input of this.getHistory()) {
let resource: URI | undefined;
if (input instanceof EditorInput) {
resource = toResource(input, { filterByScheme });

View File

@@ -100,7 +100,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
return this.labelService.getUriBasenameLabel(this.resource);
}
private dirty = false;
private dirty = this.hasAssociatedFilePath || !!this.initialValue;
private ignoreDirtyOnModelContentChange = false;
private versionId = 0;

View File

@@ -11,7 +11,6 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
@@ -120,9 +119,13 @@ suite('Untitled text editors', () => {
test('associated resource is dirty', async () => {
const service = accessor.untitledTextEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt'));
const untitled = await service.resolve({ associatedResource: file });
assert.ok(untitled.hasAssociatedFilePath);
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file }));
assert.ok(untitled.isDirty());
const model = await untitled.resolve();
assert.ok(model.hasAssociatedFilePath);
assert.equal(untitled.isDirty(), true);
untitled.dispose();
@@ -197,20 +200,14 @@ suite('Untitled text editors', () => {
const workingCopyService = accessor.workingCopyService;
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' }));
let onDidChangeDirty: IWorkingCopy | undefined = undefined;
const listener = workingCopyService.onDidChangeDirty(copy => {
onDidChangeDirty = copy;
});
assert.ok(untitled.isDirty());
// dirty
const model = await untitled.resolve();
assert.ok(model.isDirty());
assert.equal(workingCopyService.dirtyCount, 1);
assert.equal(onDidChangeDirty, model);
untitled.dispose();
listener.dispose();
model.dispose();
});