explorer: call directly onto the view instead of sending events

#93973
This commit is contained in:
isidor 2020-04-24 16:59:43 +02:00
parent f33dabe7a4
commit 25e889af45
8 changed files with 126 additions and 192 deletions

View file

@ -634,7 +634,7 @@ export interface IEditableData {
validationMessage: (value: string) => { content: string, severity: Severity } | null;
placeholder?: string | null;
startingValue?: string | null;
onFinish: (value: string, success: boolean) => void;
onFinish: (value: string, success: boolean) => Promise<void>;
}
export interface IViewPaneContainer {

View file

@ -80,10 +80,10 @@ function onError(notificationService: INotificationService, error: any): void {
notificationService.error(toErrorMessage(error, false));
}
function refreshIfSeparator(value: string, explorerService: IExplorerService): void {
async function refreshIfSeparator(value: string, explorerService: IExplorerService): Promise<void> {
if (value && ((value.indexOf('/') >= 0) || (value.indexOf('\\') >= 0))) {
// New input contains separator, multiple resources will get created workaround for #68204
explorerService.refresh();
await explorerService.refresh();
}
}
@ -98,10 +98,6 @@ export class NewFileAction extends Action {
) {
super('explorer.newFile', NEW_FILE_LABEL);
this.class = 'explorer-action ' + Codicon.newFile.classNames;
this._register(explorerService.onDidChangeEditable(e => {
const elementIsBeingEdited = explorerService.isEditable(e);
this.enabled = !elementIsBeingEdited;
}));
}
run(): Promise<void> {
@ -120,10 +116,6 @@ export class NewFolderAction extends Action {
) {
super('explorer.newFolder', NEW_FOLDER_LABEL);
this.class = 'explorer-action ' + Codicon.newFolder.classNames;
this._register(explorerService.onDidChangeEditable(e => {
const elementIsBeingEdited = explorerService.isEditable(e);
this.enabled = !elementIsBeingEdited;
}));
}
run(): Promise<void> {
@ -708,10 +700,6 @@ export class CollapseExplorerView extends Action {
@IExplorerService readonly explorerService: IExplorerService
) {
super(id, label, 'explorer-action ' + Codicon.collapseAll.classNames);
this._register(explorerService.onDidChangeEditable(e => {
const elementIsBeingEdited = explorerService.isEditable(e);
this.enabled = !elementIsBeingEdited;
}));
}
async run(): Promise<void> {
@ -735,15 +723,11 @@ export class RefreshExplorerView extends Action {
@IExplorerService private readonly explorerService: IExplorerService
) {
super(id, label, 'explorer-action ' + Codicon.refresh.classNames);
this._register(explorerService.onDidChangeEditable(e => {
const elementIsBeingEdited = explorerService.isEditable(e);
this.enabled = !elementIsBeingEdited;
}));
}
async run(): Promise<void> {
await this.viewletService.openViewlet(VIEWLET_ID);
this.explorerService.refresh();
await this.explorerService.refresh();
}
}
@ -958,7 +942,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
const onSuccess = async (value: string): Promise<void> => {
try {
const created = isFolder ? await fileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value));
refreshIfSeparator(value, explorerService);
await refreshIfSeparator(value, explorerService);
isFolder ?
await explorerService.select(created.resource, true) :
@ -968,11 +952,11 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
}
};
explorerService.setEditable(newStat, {
await explorerService.setEditable(newStat, {
validationMessage: value => validateFileName(newStat, value),
onFinish: (value, success) => {
onFinish: async (value, success) => {
folder.removeChild(newStat);
explorerService.setEditable(newStat, null);
await explorerService.setEditable(newStat, null);
if (success) {
onSuccess(value);
}
@ -994,7 +978,7 @@ CommandsRegistry.registerCommand({
}
});
export const renameHandler = (accessor: ServicesAccessor) => {
export const renameHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
const workingCopyFileService = accessor.get(IWorkingCopyFileService);
const notificationService = accessor.get(INotificationService);
@ -1005,7 +989,7 @@ export const renameHandler = (accessor: ServicesAccessor) => {
return;
}
explorerService.setEditable(stat, {
await explorerService.setEditable(stat, {
validationMessage: value => validateFileName(stat, value),
onFinish: async (value, success) => {
if (success) {
@ -1014,13 +998,13 @@ export const renameHandler = (accessor: ServicesAccessor) => {
if (stat.resource.toString() !== targetResource.toString()) {
try {
await workingCopyFileService.move(stat.resource, targetResource);
refreshIfSeparator(value, explorerService);
await refreshIfSeparator(value, explorerService);
} catch (e) {
notificationService.error(e);
}
}
}
explorerService.setEditable(stat, null);
await explorerService.setEditable(stat, null);
}
});
};

View file

@ -56,11 +56,6 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider {
this.toDispose.add(contextService.onDidChangeWorkspaceFolders(e => {
this._onDidChange.fire(e.changed.concat(e.added).map(wf => wf.uri));
}));
this.toDispose.add(explorerService.onDidChangeItem(change => {
if (change.item) {
this._onDidChange.fire([change.item.resource]);
}
}));
this.toDispose.add(explorerRootErrorEmitter.event((resource => {
this._onDidChange.fire([resource]);
})));

View file

@ -140,6 +140,7 @@ export class ExplorerView extends ViewPane {
private renderer!: FilesRenderer;
private styleElement!: HTMLStyleElement;
private treeContainer!: HTMLElement;
private compressedFocusContext: IContextKey<boolean>;
private compressedFocusFirstContext: IContextKey<boolean>;
private compressedFocusLastContext: IContextKey<boolean>;
@ -188,7 +189,7 @@ export class ExplorerView extends ViewPane {
this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService);
this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService);
this.explorerService.registerContextProvider(this);
this.explorerService.registerView(this);
}
get name(): string {
@ -248,47 +249,17 @@ export class ExplorerView extends ViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view'));
this.treeContainer = DOM.append(container, DOM.$('.explorer-folders-view'));
this.styleElement = DOM.createStyleSheet(treeContainer);
this.styleElement = DOM.createStyleSheet(this.treeContainer);
attachStyler<IExplorerViewColors>(this.themeService, { listDropBackground }, this.styleListDropBackground.bind(this));
this.createTree(treeContainer);
this.createTree(this.treeContainer);
this._register(this.labelService.onDidChangeFormatters(() => {
this._onDidChangeTitleArea.fire();
}));
this._register(this.explorerService.onDidChangeRoots(() => this.setTreeInput()));
this._register(this.explorerService.onDidChangeItem(e => {
if (this.explorerService.isEditable(undefined)) {
this.tree.domFocus();
}
this.refresh(e.recursive, e.item);
}));
this._register(this.explorerService.onDidChangeEditable(async e => {
const isEditing = !!this.explorerService.getEditableData(e);
if (isEditing) {
if (e.parent !== this.tree.getInput()) {
await this.tree.expand(e.parent!);
}
} else {
DOM.removeClass(treeContainer, 'highlight');
}
await this.refresh(false, e.parent);
if (isEditing) {
DOM.addClass(treeContainer, 'highlight');
this.tree.reveal(e);
} else {
this.tree.domFocus();
}
}));
this._register(this.explorerService.onDidSelectResource(e => this.onSelectResource(e.resource, e.reveal)));
this._register(this.explorerService.onDidCopyItems(e => this.onCopyItems(e.items, e.cut, e.previouslyCutItems)));
// Update configuration
const configuration = this.configurationService.getValue<IFilesConfiguration>();
this.onConfigurationUpdated(configuration);
@ -340,6 +311,25 @@ export class ExplorerView extends ViewPane {
return getContext(this.tree.getFocus(), this.tree.getSelection(), respectMultiSelection, this.renderer);
}
async setEditable(stat: ExplorerItem, isEditing: boolean): Promise<void> {
if (isEditing) {
if (stat.parent && stat.parent !== this.tree.getInput()) {
await this.tree.expand(stat.parent);
}
} else {
DOM.removeClass(this.treeContainer, 'highlight');
}
await this.refresh(false, stat.parent);
if (isEditing) {
DOM.addClass(this.treeContainer, 'highlight');
this.tree.reveal(stat);
} else {
this.tree.domFocus();
}
}
private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void {
if (this.autoReveal) {
const activeFile = this.getActiveFile();
@ -445,10 +435,10 @@ export class ExplorerView extends ViewPane {
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
this._register(this.tree.onDidScroll(e => {
this._register(this.tree.onDidScroll(async e => {
let editable = this.explorerService.getEditable();
if (e.scrollTopChanged && editable && this.tree.getRelativeTop(editable.stat) === null) {
editable.data.onFinish('', false);
await editable.data.onFinish('', false);
}
}));
@ -566,19 +556,18 @@ export class ExplorerView extends ViewPane {
* Refresh the contents of the explorer to get up to date data from the disk about the file structure.
* If the item is passed we refresh only that level of the tree, otherwise we do a full refresh.
*/
private refresh(recursive: boolean, item?: ExplorerItem): Promise<void> {
if (!this.tree || !this.isBodyVisible()) {
refresh(recursive: boolean, item?: ExplorerItem): Promise<void> {
if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item))) {
// Tree node doesn't exist yet
this.shouldRefresh = true;
return Promise.resolve(undefined);
}
// Tree node doesn't exist yet
if (item && !this.tree.hasNode(item)) {
return Promise.resolve(undefined);
if (this.explorerService.isEditable(undefined)) {
this.tree.domFocus();
}
const toRefresh = item || this.tree.getInput();
return this.tree.updateChildren(toRefresh, recursive);
}
@ -589,7 +578,7 @@ export class ExplorerView extends ViewPane {
return DOM.getLargestChildWidth(parentNode, childNodes);
}
private async setTreeInput(): Promise<void> {
async setTreeInput(): Promise<void> {
if (!this.isBodyVisible()) {
this.shouldRefresh = true;
return Promise.resolve(undefined);
@ -657,7 +646,7 @@ export class ExplorerView extends ViewPane {
return withNullAsUndefined(toResource(input, { supportSideBySide: SideBySideEditor.MASTER }));
}
private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise<void> {
public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise<void> {
// do no retry more than once to prevent inifinite loops in cases of inconsistent model
if (retry === 2) {
return;
@ -674,10 +663,11 @@ export class ExplorerView extends ViewPane {
.sort((first, second) => second.resource.path.length - first.resource.path.length)[0];
while (item && item.resource.toString() !== resource.toString()) {
if (item.isDisposed) {
return this.onSelectResource(resource, reveal, retry + 1);
try {
await this.tree.expand(item);
} catch (e) {
return this.selectResource(resource, reveal, retry + 1);
}
await this.tree.expand(item);
item = first(values(item.children), i => isEqualOrParent(resource, i.resource, ignoreCase));
}
@ -690,10 +680,6 @@ export class ExplorerView extends ViewPane {
try {
if (reveal) {
if (item.isDisposed) {
return this.onSelectResource(resource, reveal, retry + 1);
}
// Don't scroll to the item if it's already visible
if (this.tree.getRelativeTop(item) === null) {
this.tree.reveal(item, 0.5);
@ -703,12 +689,13 @@ export class ExplorerView extends ViewPane {
this.tree.setFocus([item]);
this.tree.setSelection([item]);
} catch (e) {
// Element might not be in the tree, silently fail
// Element might not be in the tree, try again and silently fail
return this.selectResource(resource, reveal, retry + 1);
}
}
}
private onCopyItems(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void {
itemsCopied(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void {
this.fileCopiedContextKey.set(stats.length > 0);
this.resourceCutContextKey.set(cut && stats.length > 0);
if (previousCut) {

View file

@ -78,7 +78,6 @@ export class ExplorerModel implements IDisposable {
export class ExplorerItem {
protected _isDirectoryResolved: boolean;
private _isDisposed: boolean;
public isError = false;
private _isExcluded = false;
@ -93,7 +92,6 @@ export class ExplorerItem {
private _unknown = false
) {
this._isDirectoryResolved = false;
this._isDisposed = false;
}
get isExcluded(): boolean {
@ -111,10 +109,6 @@ export class ExplorerItem {
this._isExcluded = value;
}
get isDisposed(): boolean {
return this._isDisposed;
}
get isDirectoryResolved(): boolean {
return this._isDirectoryResolved;
}
@ -257,10 +251,6 @@ export class ExplorerItem {
local.addChild(diskChild);
}
});
for (let child of oldLocalChildren.values()) {
child._dispose();
}
}
}
@ -310,20 +300,10 @@ export class ExplorerItem {
}
forgetChildren(): void {
for (let c of this.children.values()) {
c._dispose();
}
this.children.clear();
this._isDirectoryResolved = false;
}
private _dispose() {
this._isDisposed = true;
for (let child of this.children.values()) {
child._dispose();
}
}
private getPlatformAwareName(name: string): string {
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.PathCaseSensitive) ? name : name.toLowerCase();
}

View file

@ -3,13 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files';
import { IExplorerService, IFilesConfiguration, SortOrder, IExplorerView } from 'vs/workbench/contrib/files/common/files';
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
import { URI } from 'vs/base/common/uri';
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
import { FileOperationEvent, FileOperation, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
import { dirname } from 'vs/base/common/resources';
import { memoize } from 'vs/base/common/decorators';
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
@ -32,16 +32,11 @@ export class ExplorerService implements IExplorerService {
private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first
private readonly _onDidChangeRoots = new Emitter<void>();
private readonly _onDidChangeItem = new Emitter<{ item?: ExplorerItem, recursive: boolean }>();
private readonly _onDidChangeEditable = new Emitter<ExplorerItem>();
private readonly _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>();
private readonly _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>();
private readonly disposables = new DisposableStore();
private editable: { stat: ExplorerItem, data: IEditableData } | undefined;
private _sortOrder: SortOrder;
private cutItems: ExplorerItem[] | undefined;
private contextProvider: IContextProvider | undefined;
private view: IExplorerView | undefined;
private model: ExplorerModel;
constructor(
@ -59,7 +54,7 @@ export class ExplorerService implements IExplorerService {
this.disposables.add(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e)));
this.disposables.add(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IFilesConfiguration>())));
this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => {
this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(async e => {
let affected = false;
this.model.roots.forEach(r => {
if (r.resource.scheme === e.scheme) {
@ -68,50 +63,35 @@ export class ExplorerService implements IExplorerService {
}
});
if (affected) {
this._onDidChangeItem.fire({ recursive: true });
if (this.view) {
await this.view.refresh(true);
}
}
}));
this.disposables.add(this.model.onDidChangeRoots(() => {
if (this.view) {
this.view.setTreeInput();
}
}));
this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire()));
}
get roots(): ExplorerItem[] {
return this.model.roots;
}
get onDidChangeRoots(): Event<void> {
return this._onDidChangeRoots.event;
}
get onDidChangeItem(): Event<{ item?: ExplorerItem, recursive: boolean }> {
return this._onDidChangeItem.event;
}
get onDidChangeEditable(): Event<ExplorerItem> {
return this._onDidChangeEditable.event;
}
get onDidSelectResource(): Event<{ resource?: URI, reveal?: boolean }> {
return this._onDidSelectResource.event;
}
get onDidCopyItems(): Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }> {
return this._onDidCopyItems.event;
}
get sortOrder(): SortOrder {
return this._sortOrder;
}
registerContextProvider(contextProvider: IContextProvider): void {
this.contextProvider = contextProvider;
registerView(contextProvider: IExplorerView): void {
this.view = contextProvider;
}
getContext(respectMultiSelection: boolean): ExplorerItem[] {
if (!this.contextProvider) {
if (!this.view) {
return [];
}
return this.contextProvider.getContext(respectMultiSelection);
return this.view.getContext(respectMultiSelection);
}
// Memoized locals
@ -132,13 +112,18 @@ export class ExplorerService implements IExplorerService {
return this.model.findClosest(resource);
}
setEditable(stat: ExplorerItem, data: IEditableData | null): void {
async setEditable(stat: ExplorerItem, data: IEditableData | null): Promise<void> {
if (!this.view) {
return;
}
if (!data) {
this.editable = undefined;
} else {
this.editable = { stat, data };
}
this._onDidChangeEditable.fire(stat);
const isEditing = this.isEditable(stat);
await this.view.setEditable(stat, isEditing);
}
setToCopy(items: ExplorerItem[], cut: boolean): void {
@ -146,7 +131,7 @@ export class ExplorerService implements IExplorerService {
this.cutItems = cut ? items : undefined;
this.clipboardService.writeResources(items.map(s => s.resource));
this._onDidCopyItems.fire({ items, cut, previouslyCutItems });
this.view?.itemsCopied(items, cut, previouslyCutItems);
}
isCut(item: ExplorerItem): boolean {
@ -166,9 +151,12 @@ export class ExplorerService implements IExplorerService {
}
async select(resource: URI, reveal?: boolean): Promise<void> {
if (!this.view) {
return;
}
const fileStat = this.findClosest(resource);
if (fileStat) {
this._onDidSelectResource.fire({ resource: fileStat.resource, reveal });
await this.view.selectResource(fileStat.resource, reveal);
return Promise.resolve(undefined);
}
@ -190,31 +178,33 @@ export class ExplorerService implements IExplorerService {
// Update Input with disk Stat
ExplorerItem.mergeLocalWithDisk(modelStat, root);
const item = root.find(resource);
this._onDidChangeItem.fire({ item: root, recursive: true });
await this.view.refresh(true, root);
// Select and Reveal
this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal });
await this.view.selectResource(item ? item.resource : undefined, reveal);
} catch (error) {
root.isError = true;
this._onDidChangeItem.fire({ item: root, recursive: false });
await this.view.refresh(false, root);
}
}
refresh(): void {
async refresh(reveal = true): Promise<void> {
this.model.roots.forEach(r => r.forgetChildren());
this._onDidChangeItem.fire({ recursive: true });
const resource = this.editorService.activeEditor ? this.editorService.activeEditor.resource : undefined;
const autoReveal = this.configurationService.getValue<IFilesConfiguration>().explorer.autoReveal;
if (this.view) {
await this.view.refresh(true);
const resource = this.editorService.activeEditor ? this.editorService.activeEditor.resource : undefined;
const autoReveal = this.configurationService.getValue<IFilesConfiguration>().explorer.autoReveal;
if (resource && autoReveal) {
// We did a top level refresh, reveal the active file #67118
this.select(resource, true);
if (reveal && resource && autoReveal) {
// We did a top level refresh, reveal the active file #67118
this.select(resource, true);
}
}
}
// File events
private onDidRunOperation(e: FileOperationEvent): void {
private async onDidRunOperation(e: FileOperationEvent): Promise<void> {
// Add
if (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY)) {
const addedElement = e.target;
@ -224,23 +214,23 @@ export class ExplorerService implements IExplorerService {
if (parents.length) {
// Add the new file to its parent (Model)
parents.forEach(p => {
parents.forEach(async p => {
// We have to check if the parent is resolved #29177
const resolveMetadata = this.sortOrder === `modified`;
const thenable: Promise<IFileStat | undefined> = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata });
thenable.then(stat => {
if (!p.isDirectoryResolved) {
const stat = await this.fileService.resolve(p.resource, { resolveMetadata });
if (stat) {
const modelStat = ExplorerItem.create(this.fileService, stat, p.parent);
ExplorerItem.mergeLocalWithDisk(modelStat, p);
}
}
const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent);
// Make sure to remove any previous version of the file if any
p.removeChild(childElement);
p.addChild(childElement);
// Refresh the Parent (View)
this._onDidChangeItem.fire({ item: p, recursive: false });
});
const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent);
// Make sure to remove any previous version of the file if any
p.removeChild(childElement);
p.addChild(childElement);
// Refresh the Parent (View)
await this.view?.refresh(false, p);
});
}
}
@ -255,10 +245,10 @@ export class ExplorerService implements IExplorerService {
// Handle Rename
if (oldParentResource.toString() === newParentResource.toString()) {
const modelElements = this.model.findAll(oldResource);
modelElements.forEach(modelElement => {
modelElements.forEach(async modelElement => {
// Rename File (Model)
modelElement.rename(newElement);
this._onDidChangeItem.fire({ item: modelElement.parent, recursive: false });
await this.view?.refresh(false, modelElement.parent);
});
}
@ -269,11 +259,11 @@ export class ExplorerService implements IExplorerService {
if (newParents.length && modelElements.length) {
// Move in Model
modelElements.forEach((modelElement, index) => {
modelElements.forEach(async (modelElement, index) => {
const oldParent = modelElement.parent;
modelElement.move(newParents[index]);
this._onDidChangeItem.fire({ item: oldParent, recursive: false });
this._onDidChangeItem.fire({ item: newParents[index], recursive: false });
await this.view?.refresh(false, oldParent);
await this.view?.refresh(false, newParents[index]);
});
}
}
@ -282,13 +272,13 @@ export class ExplorerService implements IExplorerService {
// Delete
else if (e.isOperation(FileOperation.DELETE)) {
const modelElements = this.model.findAll(e.resource);
modelElements.forEach(element => {
modelElements.forEach(async element => {
if (element.parent) {
const parent = element.parent;
// Remove Element from Parent (Model)
parent.removeChild(element);
// Refresh Parent (View)
this._onDidChangeItem.fire({ item: parent, recursive: false });
await this.view?.refresh(false, parent);
}
});
}
@ -298,7 +288,7 @@ export class ExplorerService implements IExplorerService {
// Check if an explorer refresh is necessary (delayed to give internal events a chance to react first)
// Note: there is no guarantee when the internal events are fired vs real ones. Code has to deal with the fact that one might
// be fired first over the other or not at all.
setTimeout(() => {
setTimeout(async () => {
// Filter to the ones we care
const shouldRefresh = () => {
e = this.filterToViewRelevantEvents(e);
@ -365,8 +355,7 @@ export class ExplorerService implements IExplorerService {
};
if (shouldRefresh()) {
this.roots.forEach(r => r.forgetChildren());
this._onDidChangeItem.fire({ recursive: true });
await this.refresh(false);
}
}, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY);
}
@ -389,13 +378,13 @@ export class ExplorerService implements IExplorerService {
}));
}
private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void {
private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise<void> {
const configSortOrder = configuration?.explorer?.sortOrder || 'default';
if (this._sortOrder !== configSortOrder) {
const shouldRefresh = this._sortOrder !== undefined;
this._sortOrder = configSortOrder;
if (shouldRefresh) {
this.refresh();
await this.refresh();
}
}
}

View file

@ -11,7 +11,6 @@ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/con
import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
import { Event } from 'vs/base/common/event';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@ -38,20 +37,15 @@ export interface IExplorerService {
_serviceBrand: undefined;
readonly roots: ExplorerItem[];
readonly sortOrder: SortOrder;
readonly onDidChangeRoots: Event<void>;
readonly onDidChangeItem: Event<{ item?: ExplorerItem, recursive: boolean }>;
readonly onDidChangeEditable: Event<ExplorerItem>;
readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>;
readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>;
getContext(respectMultiSelection: boolean): ExplorerItem[];
setEditable(stat: ExplorerItem, data: IEditableData | null): void;
setEditable(stat: ExplorerItem, data: IEditableData | null): Promise<void>;
getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined;
getEditableData(stat: ExplorerItem): IEditableData | undefined;
// If undefined is passed checks if any element is currently being edited.
isEditable(stat: ExplorerItem | undefined): boolean;
findClosest(resource: URI): ExplorerItem | null;
refresh(): void;
refresh(): Promise<void>;
setToCopy(stats: ExplorerItem[], cut: boolean): void;
isCut(stat: ExplorerItem): boolean;
@ -61,11 +55,16 @@ export interface IExplorerService {
*/
select(resource: URI, reveal?: boolean): Promise<void>;
registerContextProvider(contextProvider: IContextProvider): void;
registerView(contextAndRefreshProvider: IExplorerView): void;
}
export interface IContextProvider {
export interface IExplorerView {
getContext(respectMultiSelection: boolean): ExplorerItem[];
refresh(recursive: boolean, item?: ExplorerItem): Promise<void>;
selectResource(resource: URI | undefined, reveal?: boolean): Promise<void>;
setTreeInput(): Promise<void>;
itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void;
setEditable(stat: ExplorerItem, isEditing: boolean): Promise<void>;
}
export const IExplorerService = createDecorator<IExplorerService>('explorerService');

View file

@ -693,7 +693,7 @@ namespace LabelTunnelAction {
if (context instanceof TunnelItem) {
const remoteExplorerService = accessor.get(IRemoteExplorerService);
remoteExplorerService.setEditable(context, {
onFinish: (value, success) => {
onFinish: async (value, success) => {
if (success) {
remoteExplorerService.tunnelModel.name(context.remoteHost, context.remotePort, value);
}
@ -752,7 +752,7 @@ namespace ForwardPortAction {
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }).then(tunnel => error(notificationService, tunnel, arg.remoteHost, arg.remotePort));
} else {
remoteExplorerService.setEditable(undefined, {
onFinish: (value, success) => {
onFinish: async (value, success) => {
let parsed: { host: string, port: number } | undefined;
if (success && (parsed = parseInput(value))) {
remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port));