Skip to content

Commit a34cfa4

Browse files
authored
Mini-spec for New Tab Menu Customization (microsoft#5888)
* This is a mini-spec for how I see this working * good bot * These were some typos * Addd a future consideration about the command palette and commands * Update spec to reflect discussion with Carlos * update spec to reflect investigations in Command Palette Addenda 1 * add references to microsoft#6899, and minor bits of review feedback * add `remainingProfiles` as a way of adding all the user's other profiles quickly to the menu as well * clarify why we're not doing it in the profiles list * no two commits do not contain a misspelling of separate
1 parent c241f83 commit a34cfa4

File tree

2 files changed

+300
-0
lines changed

2 files changed

+300
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
---
2+
author: Mike Griese @zadjii-msft
3+
created on: 2020-5-13
4+
last updated: 2020-08-04
5+
issue id: 1571
6+
---
7+
8+
# New Tab Menu Customization
9+
10+
## Abstract
11+
12+
Many users have lots and _lots_ of profiles that they use. Some of these
13+
profiles the user might not use that frequently. When that happens, the new tab
14+
dropdown can become quite cluttered.
15+
16+
A common ask is for the ability to reorder and reorganize this dropdown. This
17+
spec provides a design for how the user might be able to specify the
18+
customization in their settings.
19+
20+
## Inspiration
21+
22+
Largely, this spec was inspired by discussion in
23+
[#1571](https://github.com/microsoft/terminal/issues/1571#issuecomment-519504048)
24+
and the _many_ linked threads.
25+
26+
## Solution Design
27+
28+
This design proposes adding a new setting `"newTabMenu"`. When unset, (the
29+
default), the new tab menu is populated with all the profiles, in the order they
30+
appear in the users settings file. When set, this enables the user to control
31+
the appearance of the new tab dropdown. Let's take a look at an example:
32+
33+
```json
34+
{
35+
"profiles":{ ... },
36+
"newTabMenu": [
37+
{ "type":"profile", "profile": "cmd" },
38+
{ "type":"profile", "profile": "Windows PowerShell" },
39+
{ "type":"separator" },
40+
{
41+
"type":"folder",
42+
"name": "ssh",
43+
"icon": "C:\\path\\to\\icon.png",
44+
"entries":[
45+
{ "type":"profile", "profile": "Host 1" },
46+
{ "type":"profile", "profile": "8.8.8.8" },
47+
{ "type":"profile", "profile": "Host 2" }
48+
]
49+
},
50+
{ "type":"separator" },
51+
{ "type":"profile", "profile": "Ubuntu-18.04" },
52+
{ "type":"profile", "profile": "Fedora" }
53+
]
54+
}
55+
```
56+
57+
If a user were to use this as their new tab menu, that they would get is a menu
58+
that looks like this:
59+
60+
![fig 1](Menu-Customization-000.png)
61+
62+
_fig 1_: A _very rough_ mockup of what this feature might look like
63+
64+
There are five `type`s of objects in this menu:
65+
* `"type":"profile"`: This is a profile. Clicking on this entry will open a new
66+
tab, with that profile. The profile is identified with the `"profile"`
67+
parameter, which accepts either a profile `name` or GUID. The icon for this
68+
entry will be the profile's icon, and the text on the entry will be the
69+
profile's name.
70+
* `"type":"separator"`: This represents a XAML `MenuFlyoutSeparator`, enabling
71+
the user to visually space out entries.
72+
* `"type":"folder"`: This represents a nested menu of entries.
73+
- The `"name"` property provides a string of text to display for the group.
74+
- The `"icon"` property provides a path to a image to use as the icon. This
75+
property is optional.
76+
- The `"entries"` property specifies a list of menu entries that will appear
77+
nested under this entry. This can contain other `"type":"folder"` groups as
78+
well!
79+
* `"type":"action"`: This represents a menu entry that should execute a specific
80+
`ShortcutAction`.
81+
- the `id` property will specify the global action ID (see [#6899], [#7175])
82+
to identify the action to perform when the user selects the entry. Actions
83+
with invalid IDs will be ignored and omitted from the list.
84+
- The text for this entry will be the action's label (which is
85+
either provided as the `"name"` in the global list of actions, or the
86+
generated name if no `name` was provided)
87+
- The icon for this entry will similarly re-use the action's `icon`.
88+
* `"type":"remainingProfiles"`: This is a special type of entry that will be
89+
expanded to contain one `"type":"profile"` entry for every profile that was
90+
not already listed in the menu. This will allow users to add one entry for
91+
just "all the profiles they haven't manually added to the menu".
92+
- This type of entry can only be specified once - trying to add it to the menu
93+
twice will raise a warning, and ignore all but the first `remainingProfiles`
94+
entry.
95+
- This type of entry can also be set inside a `folder` entry, allowing users
96+
to highlight only a couple profiles in the top-level of the menu, but
97+
enabling all other profiles to also be accessible.
98+
- The "name" of these entries will simply be the name of the profile
99+
- The "icon" of these entries will simply be the profile's icon
100+
101+
The "default" new tab menu could be imagined as the following blob of json:
102+
103+
```json
104+
{
105+
"newTabMenu": [
106+
{ "type":"remainingProfiles" }
107+
]
108+
}
109+
```
110+
111+
### Other considerations
112+
113+
Also considered during the investigation for this feature was re-using the list
114+
of profiles to expose the structure of the new tab menu. For example, doing
115+
something like:
116+
117+
```json
118+
"profiles": {
119+
"defaults": {},
120+
"list":
121+
[
122+
{ "name": "cmd" },
123+
{ "name": "powershell" },
124+
{ "type": "separator" },
125+
{
126+
"type": "folder" ,
127+
"profiles": [
128+
{ "name": "ubuntu" }
129+
]
130+
}
131+
]
132+
}
133+
```
134+
135+
This option was not pursued because we felt that it needlessly complicated the
136+
contents of the list of profiles objects. We'd rather have the `profiles` list
137+
exclusively contain `Profile` objects, and have other elements of the json
138+
_refer_ to those profiles. What if someone would like to have an action that
139+
opened a new tab with profile index 4, and then they set that action as entry 4
140+
in the profile's list? That would certainly be some sort of unexpected behavior.
141+
142+
Additionally, what if someone wants to have an entry that opens a tab with one
143+
pane with one profile in it, and another pane with different profile in it? Or
144+
what if they want the same profile to appear twice in the menu?
145+
146+
By overloading the structure of the `profiles` list, we're forcing all other
147+
consumers of the list of profiles to care about the structure of the elements of
148+
the list. These other consumers should only really care about the list of
149+
profiles, and not necessarily how they're structured in the new tab dropdown.
150+
Furthermore, it complicates the list of profiles, by adding actions intermixed
151+
with the profiles.
152+
153+
The design chosen in this spec more cleanly separates the responsibilities of
154+
the list of profiles and the contents of the new tab menu. This way, each object
155+
can be defined independent of the structure of the other.
156+
157+
## UI/UX Design
158+
159+
See the above _figure 1_.
160+
161+
The profile's `icon` will also appear as the icon on `profile` entries. If
162+
there's a keybinding bound to open a new tab with that profile, then that will
163+
also be added to the `MenuFlyoutItem` as the accelerator text, similar to the
164+
text we have nowadays.
165+
166+
Beneath the list of profiles will _always_ be the same "Settings", "Feedback"
167+
and "About" entries, separated by a `MenuFlyoutSeparator`. This is consistent
168+
with the UI as it exists with no customization. These entries cannot be removed
169+
with this feature, only the list of profiles customized.
170+
171+
## Capabilities
172+
173+
### Accessibility
174+
175+
This menu will be added to the XAML tree in the same fashion as the current new
176+
tab flyout, so there should be no dramatic change here.
177+
178+
### Security
179+
180+
_(no change expected)_
181+
182+
### Reliability
183+
184+
_(no change expected)_
185+
186+
### Compatibility
187+
188+
_(no change expected)_
189+
190+
### Performance, Power, and Efficiency
191+
192+
_(no change expected)_
193+
194+
## Potential Issues
195+
196+
Currently, the `openTab` and `splitPane` keybindings will accept a `index`
197+
parameter to say either:
198+
* "Create a new tab/pane with the N'th profile"
199+
* "Create a new tab/pane with the profile at index N in the new
200+
tab dropdown".
201+
202+
These two were previously synonymous, as the N'th profile was always the N'th in
203+
the dropdown. However, with this change, we'll be changing the meaning of that
204+
argument to mean explicitly the first option - "Open a tab/pane with the N'th
205+
profile".
206+
207+
A previous version of this spec considered changing the meaning of that
208+
parameter to mean "open the entry at index N", the second option. However, in
209+
[Command Palette, Addendum 1], we found that naming that command would become
210+
unnecessarily complex.
211+
212+
To cover that above scenario, we could consider adding an `index` parameter to
213+
the `openNewTabDropdown` action. If specified, that would open either the N'th
214+
action in the dropdown (ignoring separators), or open the dropdown with the n'th
215+
item selected.
216+
217+
The N'th entry in the menu won't always be a profile: it might be a folder with
218+
more options, or it might be an action (that might not be opening a new tab/pane
219+
at all).
220+
221+
Given all the above scenarios, `openNewTabDropdown` with an `"index":N`
222+
parameter will behave in the following ways. If the Nth top-level entry in the
223+
new tab menu is a:
224+
* `"type":"profile"`: perform the `newTab` or `splitPane` action with that profile.
225+
* `"type":"folder"`: Focus the first element in the sub menu, so the user could
226+
navigate it with the keyboard.
227+
* `"type":"separator"`: Ignore these when counting top-level entries.
228+
* `"type":"action"`: Perform the action.
229+
230+
So for example:
231+
232+
```
233+
New Tab Button ▽
234+
├─ Folder 1
235+
│ └─ Profile A
236+
│ └─ Action B
237+
├─ Separator
238+
├─ Folder 2
239+
│ └─ Profile C
240+
│ └─ Profile D
241+
├─ Action E
242+
└─ Profile F
243+
```
244+
245+
And assuming the user has bound:
246+
```json
247+
{
248+
"bindings":
249+
[
250+
{ "command": { "action": "openNewTabDropdown", "index": 0 }, "keys": "ctrl+shift+1" },
251+
{ "command": { "action": "openNewTabDropdown", "index": 1 }, "keys": "ctrl+shift+2" },
252+
{ "command": { "action": "openNewTabDropdown", "index": 2 }, "keys": "ctrl+shift+3" },
253+
{ "command": { "action": "openNewTabDropdown", "index": 3 }, "keys": "ctrl+shift+4" },
254+
]
255+
}
256+
```
257+
258+
* <kbd>ctrl+shift+1</kbd> focuses "Profile A", but the user needs to press
259+
enter/space to creates a new tab/split
260+
* <kbd>ctrl+shift+2</kbd> focuses "Profile C", but the user needs to press
261+
enter/space to creates a new tab/split
262+
* <kbd>ctrl+shift+3</kbd> performs Action E
263+
* <kbd>ctrl+shift+4</kbd> Creates a new tab/split with Profile F
264+
265+
## Future considerations
266+
267+
* The user could set a `"name"`/`"text"`, or `"icon"` property to these menu
268+
items manually, to override the value from the profile or action. These
269+
settings would be totally optional, but it's not unreasonable that someone
270+
might want this.
271+
* We may want to consider adding a default icon for all folders or actions in
272+
the menu. For example, a folder (like 📁) for `folder` entries, or something
273+
like ⚡ for actions. We'll leave these unset by default, and evaluate setting
274+
these icons by default in the future.
275+
* Something considered during review was a way to specify "All my WSL profiles".
276+
Maybe the user wants to have all their profiles generated by the WSL Distro
277+
Generator appear in a "WSL" folder. This would likely require a more elaborate
278+
filtering syntax, to be able to select only profiles where a certain property
279+
has a specific value. Consider the user who has multiple "SSH
280+
me@\<some host\>.com" profiles, and they want all their "SSH\*" profiles to
281+
appear in an "SSH" folder. This feels out-of-scope for this spec.
282+
* A similar structure could potentially also be used for customizing the context
283+
menu within a control, or the context menu for the tab. (see [#3337])
284+
- In both of those cases, it might be important to somehow refer to the
285+
context of the current tab or control in the json. Think for example about
286+
"Close tab" or "Close other tabs" - currently, those work by _knowing_ which
287+
tab the "action" is specified for, not by actually using a `closeTab` action.
288+
In the future, they might need to be implemented as something like
289+
- Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }`
290+
- Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }`
291+
- Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }`
292+
293+
294+
<!-- Footnotes -->
295+
[#2046]: https://github.com/microsoft/terminal/issues/2046
296+
[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
297+
298+
[#3337]: https://github.com/microsoft/terminal/issues/3337
299+
[#6899]: https://github.com/microsoft/terminal/issues/6899
300+
[#7175]: https://github.com/microsoft/terminal/issues/7175
45.8 KB
Loading

0 commit comments

Comments
 (0)