diff --git a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md new file mode 100644 index 000000000..210db5499 --- /dev/null +++ b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md @@ -0,0 +1,300 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2020-5-13 +last updated: 2020-08-04 +issue id: 1571 +--- + +# New Tab Menu Customization + +## Abstract + +Many users have lots and _lots_ of profiles that they use. Some of these +profiles the user might not use that frequently. When that happens, the new tab +dropdown can become quite cluttered. + +A common ask is for the ability to reorder and reorganize this dropdown. This +spec provides a design for how the user might be able to specify the +customization in their settings. + +## Inspiration + +Largely, this spec was inspired by discussion in +[#1571](https://github.com/microsoft/terminal/issues/1571#issuecomment-519504048) +and the _many_ linked threads. + +## Solution Design + +This design proposes adding a new setting `"newTabMenu"`. When unset, (the +default), the new tab menu is populated with all the profiles, in the order they +appear in the users settings file. When set, this enables the user to control +the appearance of the new tab dropdown. Let's take a look at an example: + +```json +{ + "profiles":{ ... }, + "newTabMenu": [ + { "type":"profile", "profile": "cmd" }, + { "type":"profile", "profile": "Windows PowerShell" }, + { "type":"separator" }, + { + "type":"folder", + "name": "ssh", + "icon": "C:\\path\\to\\icon.png", + "entries":[ + { "type":"profile", "profile": "Host 1" }, + { "type":"profile", "profile": "8.8.8.8" }, + { "type":"profile", "profile": "Host 2" } + ] + }, + { "type":"separator" }, + { "type":"profile", "profile": "Ubuntu-18.04" }, + { "type":"profile", "profile": "Fedora" } + ] +} +``` + +If a user were to use this as their new tab menu, that they would get is a menu +that looks like this: + +![fig 1](Menu-Customization-000.png) + +_fig 1_: A _very rough_ mockup of what this feature might look like + +There are five `type`s of objects in this menu: +* `"type":"profile"`: This is a profile. Clicking on this entry will open a new + tab, with that profile. The profile is identified with the `"profile"` + parameter, which accepts either a profile `name` or GUID. The icon for this + entry will be the profile's icon, and the text on the entry will be the + profile's name. +* `"type":"separator"`: This represents a XAML `MenuFlyoutSeparator`, enabling + the user to visually space out entries. +* `"type":"folder"`: This represents a nested menu of entries. + - The `"name"` property provides a string of text to display for the group. + - The `"icon"` property provides a path to a image to use as the icon. This + property is optional. + - The `"entries"` property specifies a list of menu entries that will appear + nested under this entry. This can contain other `"type":"folder"` groups as + well! +* `"type":"action"`: This represents a menu entry that should execute a specific + `ShortcutAction`. + - the `id` property will specify the global action ID (see [#6899], [#7175]) + to identify the action to perform when the user selects the entry. Actions + with invalid IDs will be ignored and omitted from the list. + - The text for this entry will be the action's label (which is + either provided as the `"name"` in the global list of actions, or the + generated name if no `name` was provided) + - The icon for this entry will similarly re-use the action's `icon`. +* `"type":"remainingProfiles"`: This is a special type of entry that will be + expanded to contain one `"type":"profile"` entry for every profile that was + not already listed in the menu. This will allow users to add one entry for + just "all the profiles they haven't manually added to the menu". + - This type of entry can only be specified once - trying to add it to the menu + twice will raise a warning, and ignore all but the first `remainingProfiles` + entry. + - This type of entry can also be set inside a `folder` entry, allowing users + to highlight only a couple profiles in the top-level of the menu, but + enabling all other profiles to also be accessible. + - The "name" of these entries will simply be the name of the profile + - The "icon" of these entries will simply be the profile's icon + +The "default" new tab menu could be imagined as the following blob of json: + +```json +{ + "newTabMenu": [ + { "type":"remainingProfiles" } + ] +} +``` + +### Other considerations + +Also considered during the investigation for this feature was re-using the list +of profiles to expose the structure of the new tab menu. For example, doing +something like: + +```json +"profiles": { + "defaults": {}, + "list": + [ + { "name": "cmd" }, + { "name": "powershell" }, + { "type": "separator" }, + { + "type": "folder" , + "profiles": [ + { "name": "ubuntu" } + ] + } + ] +} +``` + +This option was not pursued because we felt that it needlessly complicated the +contents of the list of profiles objects. We'd rather have the `profiles` list +exclusively contain `Profile` objects, and have other elements of the json +_refer_ to those profiles. What if someone would like to have an action that +opened a new tab with profile index 4, and then they set that action as entry 4 +in the profile's list? That would certainly be some sort of unexpected behavior. + +Additionally, what if someone wants to have an entry that opens a tab with one +pane with one profile in it, and another pane with different profile in it? Or +what if they want the same profile to appear twice in the menu? + +By overloading the structure of the `profiles` list, we're forcing all other +consumers of the list of profiles to care about the structure of the elements of +the list. These other consumers should only really care about the list of +profiles, and not necessarily how they're structured in the new tab dropdown. +Furthermore, it complicates the list of profiles, by adding actions intermixed +with the profiles. + +The design chosen in this spec more cleanly separates the responsibilities of +the list of profiles and the contents of the new tab menu. This way, each object +can be defined independent of the structure of the other. + +## UI/UX Design + +See the above _figure 1_. + +The profile's `icon` will also appear as the icon on `profile` entries. If +there's a keybinding bound to open a new tab with that profile, then that will +also be added to the `MenuFlyoutItem` as the accelerator text, similar to the +text we have nowadays. + +Beneath the list of profiles will _always_ be the same "Settings", "Feedback" +and "About" entries, separated by a `MenuFlyoutSeparator`. This is consistent +with the UI as it exists with no customization. These entries cannot be removed +with this feature, only the list of profiles customized. + +## Capabilities + +### Accessibility + +This menu will be added to the XAML tree in the same fashion as the current new +tab flyout, so there should be no dramatic change here. + +### Security + +_(no change expected)_ + +### Reliability + +_(no change expected)_ + +### Compatibility + +_(no change expected)_ + +### Performance, Power, and Efficiency + +_(no change expected)_ + +## Potential Issues + +Currently, the `openTab` and `splitPane` keybindings will accept a `index` +parameter to say either: +* "Create a new tab/pane with the N'th profile" +* "Create a new tab/pane with the profile at index N in the new +tab dropdown". + +These two were previously synonymous, as the N'th profile was always the N'th in +the dropdown. However, with this change, we'll be changing the meaning of that +argument to mean explicitly the first option - "Open a tab/pane with the N'th +profile". + +A previous version of this spec considered changing the meaning of that +parameter to mean "open the entry at index N", the second option. However, in +[Command Palette, Addendum 1], we found that naming that command would become +unnecessarily complex. + +To cover that above scenario, we could consider adding an `index` parameter to +the `openNewTabDropdown` action. If specified, that would open either the N'th +action in the dropdown (ignoring separators), or open the dropdown with the n'th +item selected. + +The N'th entry in the menu won't always be a profile: it might be a folder with +more options, or it might be an action (that might not be opening a new tab/pane +at all). + +Given all the above scenarios, `openNewTabDropdown` with an `"index":N` +parameter will behave in the following ways. If the Nth top-level entry in the +new tab menu is a: +* `"type":"profile"`: perform the `newTab` or `splitPane` action with that profile. +* `"type":"folder"`: Focus the first element in the sub menu, so the user could + navigate it with the keyboard. +* `"type":"separator"`: Ignore these when counting top-level entries. +* `"type":"action"`: Perform the action. + +So for example: + +``` +New Tab Button ▽ +├─ Folder 1 +│ └─ Profile A +│ └─ Action B +├─ Separator +├─ Folder 2 +│ └─ Profile C +│ └─ Profile D +├─ Action E +└─ Profile F +``` + +And assuming the user has bound: +```json +{ + "bindings": + [ + { "command": { "action": "openNewTabDropdown", "index": 0 }, "keys": "ctrl+shift+1" }, + { "command": { "action": "openNewTabDropdown", "index": 1 }, "keys": "ctrl+shift+2" }, + { "command": { "action": "openNewTabDropdown", "index": 2 }, "keys": "ctrl+shift+3" }, + { "command": { "action": "openNewTabDropdown", "index": 3 }, "keys": "ctrl+shift+4" }, + ] +} +``` + +* ctrl+shift+1 focuses "Profile A", but the user needs to press + enter/space to creates a new tab/split +* ctrl+shift+2 focuses "Profile C", but the user needs to press + enter/space to creates a new tab/split +* ctrl+shift+3 performs Action E +* ctrl+shift+4 Creates a new tab/split with Profile F + +## Future considerations + +* The user could set a `"name"`/`"text"`, or `"icon"` property to these menu + items manually, to override the value from the profile or action. These + settings would be totally optional, but it's not unreasonable that someone + might want this. +* We may want to consider adding a default icon for all folders or actions in + the menu. For example, a folder (like 📁) for `folder` entries, or something + like ⚡ for actions. We'll leave these unset by default, and evaluate setting + these icons by default in the future. +* Something considered during review was a way to specify "All my WSL profiles". + Maybe the user wants to have all their profiles generated by the WSL Distro + Generator appear in a "WSL" folder. This would likely require a more elaborate + filtering syntax, to be able to select only profiles where a certain property + has a specific value. Consider the user who has multiple "SSH + me@\.com" profiles, and they want all their "SSH\*" profiles to + appear in an "SSH" folder. This feels out-of-scope for this spec. +* A similar structure could potentially also be used for customizing the context + menu within a control, or the context menu for the tab. (see [#3337]) + - In both of those cases, it might be important to somehow refer to the + context of the current tab or control in the json. Think for example about + "Close tab" or "Close other tabs" - currently, those work by _knowing_ which + tab the "action" is specified for, not by actually using a `closeTab` action. + In the future, they might need to be implemented as something like + - Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }` + - Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }` + - Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }` + + + +[#2046]: https://github.com/microsoft/terminal/issues/2046 +[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md + +[#3337]: https://github.com/microsoft/terminal/issues/3337 +[#6899]: https://github.com/microsoft/terminal/issues/6899 +[#7175]: https://github.com/microsoft/terminal/issues/7175 diff --git a/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png b/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png new file mode 100644 index 000000000..54ade166f Binary files /dev/null and b/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png differ