Auto-name durable tab sessions from shell-integration context - #660
Auto-name durable tab sessions from shell-integration context#660Yuandi (DDKinger) wants to merge 1 commit into
Conversation
The recents list showed whatever the shell reported as the console title,
so every entry read "PowerShell" and the list was useless for finding a
session again.
Generate the name at save time instead, by combining structured context we
already own into "branch - task":
- branch comes from reading .git/HEAD under the pane's working directory
- task comes from the shell-integration command marks, mapped through a
small hand-maintained dictionary (npm run dev -> "dev server",
ssh box01 -> "box01", python -m pytest -> "python tests")
No AI and no network: same inputs always produce the same name.
The working directory is deliberately not a segment, because the recents
list already renders it in its own column beside the name.
Notes:
- Naming happens only in _PersistDurableTabSession, which runs once per
tab close. That is what makes the git read and the comparatively
expensive CommandHistory() call affordable, and it keeps the live tab
strip untouched.
- .git may be a *file* ("gitdir: ...") for linked worktrees; this repo is
developed that way, so that form is handled and covered by a test.
- A tab the user renamed by hand keeps its name verbatim.
- Unrecognized commands fall back to the truncated original, so the worst
case is showing the command that was run - never an empty or wrong label.
- Dictionary labels are English literals; the commands they describe are
English too, and localizing them would cost more than the table is worth.
- The recents row now reserves width for the cwd, since generated names are
long enough to have pushed it out of the row entirely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c9f87f86-1d13-463f-9d26-1462f568584e
|
|
||
| // Branch names legitimately contain slashes, and the whole thing after | ||
| // refs/heads/ is the name - not just the trailing segment. | ||
| VERIFY_ARE_EQUAL(std::wstring{ L"dev/yuazha/tab-auto-naming" }, |
| // Branch names legitimately contain slashes, and the whole thing after | ||
| // refs/heads/ is the name - not just the trailing segment. | ||
| VERIFY_ARE_EQUAL(std::wstring{ L"dev/yuazha/tab-auto-naming" }, | ||
| ParseGitHeadContent(L"ref: refs/heads/dev/yuazha/tab-auto-naming\n").value()); |
|
|
||
| // `.git` file pointer, used by linked worktrees and submodules. | ||
| VERIFY_ARE_EQUAL(std::wstring{ L"C:/repo/.git/worktrees/feature" }, | ||
| ParseGitDirPointer(L"gitdir: C:/repo/.git/worktrees/feature\n").value()); |
| std::error_code error; | ||
| const auto root = std::filesystem::temp_directory_path(error) / | ||
| (L"tab-auto-naming-" + std::to_wstring(GetCurrentProcessId())); | ||
| const auto realGitDirectory = root / L"realgit"; |
| std::ofstream head{ realGitDirectory / L"HEAD", std::ios::binary }; | ||
| head << "ref: refs/heads/worktree-branch\n"; | ||
| std::ofstream pointer{ checkout / L".git", std::ios::binary }; | ||
| pointer << "gitdir: " << realGitDirectory.string() << "\n"; |
|
|
||
| // Builds the name a durable session is saved under. | ||
| // | ||
| // The shell-reported title alone makes the recents list a wall of identical |
| // The shell-reported title alone makes the recents list a wall of identical | ||
| // shell names, so we combine the structured context shell integration | ||
| // already gives us into "branch · task". The working directory is not part | ||
| // of the name because the recents list renders it in its own column. |
| if (const auto history = shellControl.CommandHistory()) | ||
| { | ||
| // A command still running at close time is the best description of | ||
| // the tab. Otherwise the user is sitting at an idle prompt, and the |
| fn long_generated_name_does_not_hide_the_cwd() { | ||
| let mut item = summary(); | ||
| // What auto-naming now produces: "branch · task". | ||
| item.name = "dev/yuazha/tab-auto-naming \u{b7} dev server".to_string(); |
| #[test] | ||
| fn name_may_use_the_full_column_without_a_cwd() { | ||
| let mut item = summary(); | ||
| item.name = "dev/yuazha/tab-auto-naming \u{b7} dev server".to_string(); |
check-spelling-bot Report
|
| Dictionary | Entries | Covers | Uniquely |
|---|---|---|---|
| cspell:csharp/csharp.txt | 32 | 2 | 2 |
| cspell:aws/aws.txt | 232 | 2 | 2 |
| cspell:fonts/fonts.txt | 536 | 1 | 1 |
Consider adding to the extra_dictionaries array (in the .github/actions/spelling/config.json file):
"cspell:csharp/csharp.txt",
"cspell:aws/aws.txt",
"cspell:fonts/fonts.txt",
To stop checking additional dictionaries, put (in the .github/actions/spelling/config.json file):
"check_extra_dictionaries": []Forbidden patterns 🙅 (1)
In order to address this, you could change the content to not match the forbidden patterns (comments before forbidden patterns may help explain why they're forbidden), add patterns for acceptable instances, or adjust the forbidden patterns themselves.
These forbidden patterns matched content:
Should probably be Otherwise,
(?<=\. )Otherwise\s
Pattern suggestions ✂️ (1)
You could add these patterns to .github/actions/spelling/patterns/b4ddfa82a7224f3e43e8215afa15bf4399826e02.txt:
# Automatically suggested patterns
# hit-count: 1 file-count: 1
# container images
image: [-\w./:@]+
Alternatively, if a pattern suggestion doesn't make sense for this project, add a # to the beginning of the line in the candidates file with the pattern to stop suggesting it.
Errors, Warnings, and Notices ❌ (3)
See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.
| ❌ Errors, Warnings, and Notices | Count |
|---|---|
| ℹ️ candidate-pattern | 1 |
| 54 | |
| ❌ forbidden-pattern | 1 |
See ❌ Event descriptions for more information.
✏️ Contributor please read this
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
If the listed items are:
- ... misspelled, then please correct them instead of using the command.
- ... names, please add them to
.github/actions/spelling/allow/names.txt. - ... APIs, you can add them to a file in
.github/actions/spelling/allow/. - ... just things you're using, please add them to an appropriate file in
.github/actions/spelling/expect/. - ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in
.github/actions/spelling/patterns/.
See the README.md in each directory for more information.
🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉
If the flagged items are 🤯 false positives
If items relate to a ...
-
binary file (or some other file you wouldn't want to check at all).
Please add a file path to the
excludes.txtfile matching the containing file.File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.
^refers to the file's path from the root of the repository, so^README\.md$would exclude README.md (on whichever branch you're using). -
well-formed pattern.
If you can write a pattern that would match it,
try adding it to thepatterns.txtfile.Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
Problem
Durable tab sessions were saved under whatever the shell reported as the console title. Every entry in the recents list read
PowerShell, which made the list useless for finding a session again.Approach
Generate the name at save time by combining structured context we already own, into
branch · task:branch.git/HEADunder the pane''s working directorytaskNo AI and no network — the same inputs always produce the same name.
The dictionary maps common commands to friendly labels (
npm run dev→dev server,ssh box01→box01,python -m pytest→python tests,docker compose up→compose), after normalizing: stripsudo/env X=Y/npx-style prefixes, and keep only the first command of a chain.The working directory is deliberately not a segment
The recents list already renders
active_pane_cwdin its own column next to the name, and search already matches both. Putting the repo in the name too would just duplicate what the row shows — so the incremental value isbranch+task.Why save time, and not the live tab title
Naming happens only in
_PersistDurableTabSession, which runs once per tab close. That is what makes the.git/HEADread and the comparatively expensiveCommandHistory()call affordable, and it leaves the live tab strip completely untouched — no debouncing, no git caching, no title churn while a command runs.Notes
.gitmay be a file (gitdir: ...) for linked worktrees and submodules. This repository is developed that way, so that form is handled and covered by a test — without it, naming would silently not work for anyone working in a.worktree/<name>checkout.Tab::GetTabText())..reswlocalization would cost more than the table itself is worth. Easy to revisit._PersistDurableTabSessionis now extracted into_FindDescribingShellControl, so the name andactive_pane_cwdcome from the same pane.Validation
TerminalAppLibandTerminalApp.LocalTestsbuild clean.durable_tab_sessions_viewtests: 10/10 (2 new).TabTests— the suite has 32 pre-existing failures in this environment (_initializeTerminalPagefails with0x8000ffff). After the change: still 32 failures, passed 13 → 16. No regression.Not covered
_BuildDurableTabSessionNameitself has no unit test, because it needs a realTermControl. The same pre-existing_initializeTerminalPagefailures block adding an integration test here. The pure helpers are well covered, but the wiring itself currently rests on code review.