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

Add settings to control the tab subtitle style (#12965) #33815

Merged

Conversation

tvald
Copy link
Contributor

@tvald tvald commented Sep 4, 2017

Fixes #1469

Adds a template system for tab descriptions:

  • ${pathShort}: e.g. parent
  • ${pathMedium}: e.g. more/parent
  • ${pathLong}: e.g. workspace/even/more/parent
  • ${pathDifferential}: e.g. .../parent, when another tab shares the same title (current behavior)

@msftclas
Copy link

msftclas commented Sep 4, 2017

@tvald,
Thanks for having already signed the Contribution License Agreement. Your agreement has not been validated yet. We will now review your pull request.
Thanks,
Microsoft Pull Request Bot

@tvald
Copy link
Contributor Author

tvald commented Sep 4, 2017

To anticipate a couple of design questions:

  1. Why use a template instead of a simple enumeration?

The template system is more flexible and future-proof than an enumeration. The comparable configuration window.title also uses a template system.

  1. Is ${pathMedium} useful?

Probably not. My initial impulse was to provide ancestor directories via ${path[0..N]}, where ${path[0]} = 'parent' all the way up to ${path[N]} = 'workspace'. However, this would abuse the template system with a faux-array syntax. I settled on pathMedium as an inoffensive partial solution for anyone who wants something in-between pathShort and pathLong.

I would prefer to see a minimal feature accepted and merged. It can then be expanded with further PRs as users figure out what they more want in the tab description.

@bpasero bpasero added this to the On Deck milestone Sep 5, 2017
@bpasero bpasero self-assigned this Sep 5, 2017
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.

Good start, some feedback provided.

@@ -104,6 +104,16 @@ let workbenchProperties: { [path: string]: IJSONSchema; } = {
'description': nls.localize('showEditorTabs', "Controls if opened editors should show in tabs or not."),
'default': true
},
'workbench.editor.tabDescription': {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder how clear this setting for users is. I know we call this "description" internally, but unsure if people will relate this to the small text next to the main label of a tab.

We already have window.title, maybe this should be workbench.editor.tabSubtitle ?

@@ -211,6 +214,10 @@ export class TabsTitleControl extends TitleControl {
}
}));

// Configuration updates
Copy link
Member

Choose a reason for hiding this comment

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

Can we try to put this into the ITabOptions bag and use the related IEditorGroupsService.onTabOptionsChanged event?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked into this briefly, and I don't know how to wire it all up. Sorry.

Copy link
Member

Choose a reason for hiding this comment

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

ok leave it as is and I will have a look after you are done 👍

@@ -211,6 +214,10 @@ export class TabsTitleControl extends TitleControl {
}
}));

// Configuration updates
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged()));
Copy link
Member

Choose a reason for hiding this comment

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

I typically prefer to setup listeners from the constructor via registerListeners (see in parent class: https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/browser/parts/editor/titleControl.ts#L146). Can we do the same in this class and call the super one?

Copy link
Contributor Author

@tvald tvald Sep 6, 2017

Choose a reason for hiding this comment

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

I attempted this like so:

	protected registerListeners(): void {
		super.registerListeners();

		// Configuration updates
		this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged()));
		this.onConfigurationChanged(false);
	}

Which results in the following error on startup:

Cannot read property 'onDidUpdateConfiguration' of undefined
    at TabsTitleControl.registerListeners (file:///Users/tvald/Documents/Tuple/git/vscode/out/vs/workbench/browser/parts/editor/tabsTitleControl.js:47:57)

It appears that configurationService isn't yet initialized when registerListeners() is called from super(). I haven't looked into the injection framework, so I don't know why configurationService is different from everything else being injected.

Copy link
Member

Choose a reason for hiding this comment

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

ok leave it as is and I will have a look after you are done 👍

@@ -211,6 +214,10 @@ export class TabsTitleControl extends TitleControl {
}
}));

// Configuration updates
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged()));
this.onConfigurationChanged();
Copy link
Member

Choose a reason for hiding this comment

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

This call seems to cause an update even though we are in the middle of creating the control. I would add an extra flag to not cause an update if this method is being called from construction phase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in d925602

});
}
}
});

for (const label of labels) {
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be a very specific way of treating description labels by using the native path separator. You have to keep in mind though that there are editors that are not about files (e.g. extension editor).

I think a better approach is to push the logic of a short, medium or long description to each EditorInput. If you see how it is done for the window title (FileEditorInput.getTitle()) we should do something similar for the description imho. We can just change the getDescription signature to accept short, medium or long version (we already have a verbose flag which is going into that direction).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That does sound better, but it's more involved than I can commit to.

Copy link
Member

Choose a reason for hiding this comment

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

As I mentioned before, just move the logic into FileEditorInput, no need to implement it for all other editors.

'default': '${pathDifferential}',
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'tabDescription' },
`Controls tab descriptions. Variables are substituted based on the context:
\${pathShort}: e.g. parent
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be a very specific way of treating description labels by using the native path separator. You have to keep in mind though that there are editors that are not about files (e.g. extension editor). I would rather call this:

  • descriptionShort
  • descriptionMedium
  • descriptionLong
  • descriptionDifferential

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do note that descriptions are already assumed to be paths when they're trimmed via shorten. That said, the architecture you mention below sounds sufficiently flexible and could be augmented to provide an editor-type-specific shorten method as well. Unfortunately, that's more involved than I can commit to.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I agree that the shorten logic is assuming paths but that is only because this algorithm can only work with paths and nothing else, so I think that is fair. But other editors might have the desire to provide short, medium and long descriptions.

I think the change is straightforward: I am not asking to implement this for each and every editor, it is fine to just move your logic into FileEditorInput.

- Change configuration type to the enum ['default', 'short', 'medium', 'long']
- Move path logic into FileEditorInput.
- Move configuration handling into GroupService/ITabOptions.
- Refactor duplicate path shortening logic.
@tvald
Copy link
Contributor Author

tvald commented Sep 7, 2017

The requested changes are done in d8b5863. This required a couple of significant changes:

  • the configuration value is now just an enum 'default' | 'short' | 'medium' | 'long'
  • long uses the absolute path, while medium uses a path relative to the workspace root

@bpasero
Copy link
Member

bpasero commented Sep 7, 2017

@tvald thanks, I will have a look tomorrow.

… show subtitle when all descriptions are identical.
@tvald
Copy link
Contributor Author

tvald commented Sep 8, 2017

Added a couple of small fixes to handle corner cases in the de-duplication logic.

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.

@tvald this took me quite some time to review, I left some comments.

Can you elaborate why you decided to rewrite the code within the if (shortenDuplicates) { branch from the version I had before? It seems to me that you could have left that code intact and just use getDescription(LONG) instead of getDescription(true) for picking the full description when there are duplicates.

'enum': ['default', 'short', 'medium', 'long'],
'default': 'default',
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'tabDescription' },
`Controls tab subtitle format:
Copy link
Member

Choose a reason for hiding this comment

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

Maybe a bit more verbose to understand the setting: Controls the format of the subtitle for an editor tab. Subtitles show up next to the file name depending on this setting and make it easier to understand the location of a file:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

short: 'parent'
medium: 'workspace/src/parent'
long: '/home/user/workspace/src/parent'
default: '.../parent', when another tab shares the same title`),
Copy link
Member

Choose a reason for hiding this comment

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

Prefix the enumeration values with "-" to be consistent with other settings where we do that, e.g. workbench.fontAliasing setting below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

@@ -104,6 +104,17 @@ let workbenchProperties: { [path: string]: IJSONSchema; } = {
'description': nls.localize('showEditorTabs', "Controls if opened editors should show in tabs or not."),
'default': true
},
'workbench.editor.tabSubtitle': {
Copy link
Member

Choose a reason for hiding this comment

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

Please also define the enumDescriptions property to get more help when typing. See https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/electron-browser/main.contribution.ts#L229 for example how we use it for the workbench.fontAliasing setting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

@@ -76,7 +75,7 @@ export class TabsTitleControl extends TitleControl {
@IWindowsService private windowsService: IWindowsService,
@IThemeService themeService: IThemeService,
@IFileService private fileService: IFileService,
@IWorkspacesService private workspacesService: IWorkspacesService
@IWorkspacesService private workspacesService: IWorkspacesService,
Copy link
Member

Choose a reason for hiding this comment

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

Looks like an extra trailing comma.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

// identify duplicate titles
const mapTitleToDuplicates = new Map<string, AugmentedLabel[]>();
for (const label of labels) {
getOrSet(mapTitleToDuplicates, label.name, []).push(label);
Copy link
Member

Choose a reason for hiding this comment

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

I think we can already here ignore any IEditorLabel that does not have a description. Because further below there is a check:

if (typeof label.description !== 'string' || !label.description) {
   label.description = '';
}

and then later:

duplicateTitles = duplicateTitles.filter(label => label.description);

If we would just ignore labels without description in the code path of handling duplicates I think we can simplify this a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

const shortenedDescriptions = shorten(descriptions);
descriptions.forEach((description, i) => {
const duplicateDescriptions = mapDescriptionToDuplicates.get(description);
duplicateDescriptions.map(label => label.description = shortenedDescriptions[i]);
Copy link
Member

Choose a reason for hiding this comment

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

Use forEach() here and not map()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 26db623

@tvald
Copy link
Contributor Author

tvald commented Sep 10, 2017

Can you elaborate why you decided to rewrite the code within the if (shortenDuplicates) { branch from the version I had before?

There's probably a clean way to retain the old code, if that's desired. Some small rearrangement was necessary to switch from the template system to the enum configuration, and at the time it seemed cleaner to rewrite the whole section. I'm not attached to the new code.

@bpasero
Copy link
Member

bpasero commented Sep 11, 2017

@tvald thanks, I will keep your changes in around handling duplicates.

@bpasero bpasero merged commit 3d6f59a into microsoft:master Sep 11, 2017
@bpasero bpasero modified the milestones: September 2017, On Deck Sep 11, 2017
@bpasero bpasero changed the title Add template system for tab descriptions. (#12965) Add settings to control the tab subtitle style (#12965) Sep 11, 2017
@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.

Always show path information even for non-focus editor
3 participants