Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various kinds of sorting in explorer #29509

Merged
merged 13 commits into from
Jul 6, 2017
Merged

Conversation

foreverest
Copy link
Contributor

@foreverest foreverest commented Jun 27, 2017

Addresses #29329, #5222, #22529, #11823

@mention-bot
Copy link

@foreverest, thanks for your PR! By analyzing the history of the files in this pull request, we identified @egamma and @bpasero to be potential reviewers.

@msftclas
Copy link

@foreverest,
Thanks for your contribution.
To ensure that the project team has proper rights to use your work, please complete the Contribution License Agreement at https://cla.microsoft.com.

It will cover your contributions to all Microsoft-managed open source projects.
Thanks,
Microsoft Pull Request Bot

@msftclas
Copy link

@foreverest, thanks for signing the contribution license agreement. We will now validate the agreement and then the pull request.

Thanks, Microsoft Pull Request Bot

@bpasero bpasero added this to the On Deck milestone Jun 30, 2017
@bpasero
Copy link
Member

bpasero commented Jun 30, 2017

@foreverest good start but I think we can do more. There is a lot of people that have asked for sorting the explorer and I think instead of introducing a setting for each we should have one setting explorer.sortOrder with an enum of possible values:

What do you think? They should all be straight forward, except for maybe the modified one that probably needs a file change listener to resort based on file access.

@foreverest
Copy link
Contributor Author

@bpasero sounds great! Let me do this.

@foreverest
Copy link
Contributor Author

foreverest commented Jul 1, 2017

@bpasero I believe we should enhance sorting config by expanding in three dimensions. To cover most needs we can have something like this:

sortOrder: asc, desc
sortBy: name, type, modified, (size?)
fileDirOrder: dirsFirst, filesFirst, mixed

What do you think?

@bpasero
Copy link
Member

bpasero commented Jul 1, 2017

@foreverest what is the motivation for allowing to change sorting from ascending/descending? I have not heard a feature request for that so far. I think that rather belongs to a pure file explorer than an editor. Arguably, even sorting by type and modified date is already stretch for an editor.

@foreverest
Copy link
Contributor Author

foreverest commented Jul 1, 2017

@bpasero as of now, the ascending order is used in all sortings. But for modified sorting it might be more useful to have descending option. So that the most recently changed files appeared at the top.

@bpasero
Copy link
Member

bpasero commented Jul 1, 2017

@foreverest ah yeah, I was assuming that the order for "modified" actually is descending by default without an extra option to change that. overall I was hoping we could get this into a single option and not start to introduce many.

@foreverest
Copy link
Contributor Author

@bpasero I'm done with this

Copy link
Member

@bpasero bpasero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good start, some comments to make this a little bit better 👍

@@ -60,6 +60,7 @@ export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEdit
};
autoReveal: boolean;
enableDragAndDrop: boolean;
sortOrder: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be sortOrder: 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type will appear in three places:

  1. IFilesConfiguration interface
  2. ExplorerView class
  3. The sorter class
    Should I define a type alias like this type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense to me.

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move this out of this new file and into src/vs/workbench/parts/files/common/files.ts

// Explorer Sorter
export class FileSorter implements ISorter {
// Default Sorter
export class DefaultSorter implements ISorter {
Copy link
Member

@bpasero bpasero Jul 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is lots of duplication across all sorters, specifically this code:

if (statA instanceof NewStatPlaceholder) {
	return -1;
}

if (statB instanceof NewStatPlaceholder) {
	return 1;
}

// Do not sort roots
if (statA.isRoot) {
	return -1;
}

if (statB.isRoot) {
	return 1;
}

I suggest to reduce the number of filters to just one which listens to configuration changes and updates itself based on the config. That avoids the extra call to setSorter() in the tree and makes the sorter self contained based on the configuration.

@@ -82,6 +83,8 @@ export class ExplorerView extends CollapsibleView {

private autoReveal: boolean;

private sortOrder: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this sortOrder: 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'

if (this.sortOrder !== configSortOrder) {
this.sortOrder = configSortOrder;
const sorter = this.getSorter(this.sortOrder);
(<Tree>this.explorerViewer).setSorter(sorter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting a different sorter after the configuration changed, I suggest to actually let the sorter implement the listening and update itself based on the config. You still need this code to decide to do the refresh, which is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still need this code to decide to do the refresh

Do you mean to leave this code here, or move it to the sorter class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, move it to the sorter class. But you still need that code to trigger a refresh of the explorer when the sorting config changed and imho it is fine that the explorerView triggers this and not the sorter.

@@ -364,6 +364,10 @@ export class Tree extends Events.EventEmitter implements _.ITree {
return this.model.hasTrait(trait, element);
}

public setSorter(sorter: _.ISorter): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not be needed once the sorter is configuring itself.

@@ -430,6 +443,21 @@ export class ExplorerView extends CollapsibleView {
return this.explorerViewer;
}

private getSorter(sortOrder: string): ISorter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed once the sorter configures itself based on the config.

if (e.gotUpdated()) {
// Check updated only if sortOrder === MODIFIED
const config = this.configurationService.getConfiguration<IFilesConfiguration>();
if (config && config.explorer && config.explorer.sortOrder === SortOrderConfiguration.MODIFIED) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to resolve the config here again? You should be able to just use this.sortOrder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right! 👍

}

if (statA.mtime === statB.mtime) {
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense here to fallback to the default sorting by file name in case the modified time is the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure!

@bpasero bpasero modified the milestones: On Deck, July 2017 Jul 3, 2017
@foreverest foreverest changed the title Make listing directories first an option Various kinds of sorting in explorer Jul 3, 2017
Copy link
Contributor Author

@foreverest foreverest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bpasero I've made the changes. Please review.

Copy link
Member

@bpasero bpasero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost, good stuff 👍

}

if (oneName === otherName) {
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not make sense to use the filename compare fallback here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I do use it here. First, I compare extensions on line 104. And if they are equal, I compare filenames on lines 108-112.

nls.localize('sortOrder.type', 'Files and directories are sorted by their extensions, in alphabetical order. Directories are displayed before files.'),
nls.localize('sortOrder.modified', 'Files and directories are sorted by last modified date, in descending order. Directories are displayed before files.')
],
'description': nls.localize('sortOrder', "Controls the way of sorting files and directories.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest: Controls the way of sorting files and directories in the explorer.

if (statA.isDirectory && !statB.isDirectory) {
return -1;
}
switch (this.sortOrder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate that we now can do this all from a single compare implementation, that is very cool. However, I am having some difficulties that we have the switch (this.sortOrder) twice in the method, makes it very hard to understand the flow imho. Can we:

  • move the things which are independent from the sort order up (NewStatPlaceholder and roots)
  • have a single switch statement and not two?

I know that having a single switch statement might mean some duplication of sorting logic, but I think it would still be easier to understand. We can still in that model extract logic into methods if we wanted to share.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the order matters here. If I do this, it will affect the position of the new file/dir placeholder. It will appear at the very top of the current dir, not after all subdirs as currently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To implement the approach you described and keep current behavior of placeholders, the code may look like this:

public compare(tree: ITree, statA: FileStat, statB: FileStat): number {
	// Do not sort roots
	if (statA.isRoot) {
		return -1;
	}
	if (statB.isRoot) {
		return 1;
	}

	switch (this.sortOrder) {
		case 'default':
			if (statA.isDirectory && !statB.isDirectory) {
				return -1;
			}
			if (statB.isDirectory && !statA.isDirectory) {
				return 1;
			}
			if (statA instanceof NewStatPlaceholder) {
				return -1;
			}
			if (statB instanceof NewStatPlaceholder) {
				return 1;
			}
			return comparers.compareFileNames(statA.name, statB.name);

		case 'type':
			if (statA.isDirectory && !statB.isDirectory) {
				return -1;
			}
			if (statB.isDirectory && !statA.isDirectory) {
				return 1;
			}
			if (statA instanceof NewStatPlaceholder) {
				return -1;
			}
			if (statB instanceof NewStatPlaceholder) {
				return 1;
			}
			return comparers.compareFileExtensions(statA.name, statB.name);

		case 'modified':
			if (statA.isDirectory && !statB.isDirectory) {
				return -1;
			}
			if (statB.isDirectory && !statA.isDirectory) {
				return 1;
			}
			if (statA instanceof NewStatPlaceholder) {
				return -1;
			}
			if (statB instanceof NewStatPlaceholder) {
				return 1;
			}
			if (statA.mtime !== statB.mtime) {
				return statA.mtime < statB.mtime ? 1 : -1;
			} else {
				return comparers.compareFileNames(statA.name, statB.name);
			}

		case 'filesFirst':
			if (statA.isDirectory && !statB.isDirectory) {
				return 1;
			}
			if (statB.isDirectory && !statA.isDirectory) {
				return -1;
			}
			if (statA instanceof NewStatPlaceholder) {
				return -1;
			}
			if (statB instanceof NewStatPlaceholder) {
				return 1;
			}
			return comparers.compareFileNames(statA.name, statB.name);

		case 'mixed':
			if (statA instanceof NewStatPlaceholder) {
				return -1;
			}
			if (statB instanceof NewStatPlaceholder) {
				return 1;
			}
			return comparers.compareFileNames(statA.name, statB.name);
	}
}

Yeah, it looks more verbose but definitely less confusing. Should I commit this?

@bpasero
Copy link
Member

bpasero commented Jul 6, 2017

@foreverest nah, it's fine, I will merge it, thanks.

@wclr
Copy link

wclr commented Jul 12, 2017

@bpasero
if set sortOrder to type
dot folders come after ordinary folders:
image

Which is a bit unconventional. While dot files come first.

@bpasero
Copy link
Member

bpasero commented Jul 12, 2017

@whitecolor good catch, can you file an issue? @foreverest do you want to take a look through a PR?

@foreverest
Copy link
Contributor Author

foreverest commented Jul 12, 2017

@whitecolor That's because currently folders are handled in the same way as files. Namely, the algorithm first looks at extensions, then at names. In your case reader and shared folders have no extensions, while other ones have. So according to current algorithm that's correct behavior.
However, I think it makes sense not to apply the same rules to files and folders, since the term type is not fully applicable to folders.
@bpasero Should I change the algorithm so that it sorts folders in normal alphabetical order?

P.S.
@whitecolor

While dot files come first.

That's not correct.
image

@gulshan
Copy link

gulshan commented Jul 12, 2017

IMHO, extensions within folder name should not matter. They should be ordered by name alphabatecally.

@bpasero
Copy link
Member

bpasero commented Jul 13, 2017

@foreverest yes I think folders with a leading "." should show before folders that do not have any extension. Thanks 👍

@wclr
Copy link

wclr commented Jul 13, 2017

IMHO, extensions within folder name should not matter. They should be ordered by name alphabatecally.

I believe sometimes it may be useful to have folders sorted by extension too.
But there may be introduced an option for example folderSortOrder.

@sylvain-pauly
Copy link

Well, another sort type would be great : the default one, except it would be case sensitive. Every folders starting with a capital letter first. Github follow this rule if I'm not mistaken :) I would love to have this in VScode (which is awesome BTW).

@github-actions github-actions bot locked and limited conversation to collaborators Mar 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants