Skip to content

Auto-name durable tab sessions from shell-integration context - #660

Open
Yuandi (DDKinger) wants to merge 1 commit into
dev/yuazha/durable-session-restorefrom
dev/yuazha/tab-auto-naming
Open

Auto-name durable tab sessions from shell-integration context#660
Yuandi (DDKinger) wants to merge 1 commit into
dev/yuazha/durable-session-restorefrom
dev/yuazha/tab-auto-naming

Conversation

@DDKinger

Copy link
Copy Markdown
Contributor

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:

Segment Source
branch .git/HEAD under the pane''s working directory
task shell-integration command marks, through a small hand-maintained dictionary

No AI and no network — the same inputs always produce the same name.

The dictionary maps common commands to friendly labels (npm run devdev server, ssh box01box01, python -m pytestpython tests, docker compose upcompose), after normalizing: strip sudo / 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_cwd in 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 is branch + 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/HEAD read and the comparatively expensive CommandHistory() call affordable, and it leaves the live tab strip completely untouched — no debouncing, no git caching, no title churn while a command runs.

Notes

  • .git may 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.
  • A tab the user renamed by hand keeps its name verbatim (via the existing Tab::GetTabText()).
  • Unrecognized commands fall back to the truncated original, so the worst case is "shows the command you ran" — never an empty or wrong label.
  • Dictionary labels are English literals. The commands they describe are English too, and routing them through .resw localization would cost more than the table itself is worth. Easy to revisit.
  • The recents row now reserves width for the cwd. Generated names are long enough that they previously pushed the cwd column out of the row entirely, and the cwd is what distinguishes two sessions on the same branch.
  • The duplicated "pick the describing pane" logic in _PersistDurableTabSession is now extracted into _FindDescribingShellControl, so the name and active_pane_cwd come from the same pane.

Validation

  • TerminalAppLib and TerminalApp.LocalTests build clean.
  • 3 new TAEF tests pass, including an end-to-end test over a real worktree layout on disk.
  • Rust durable_tab_sessions_view tests: 10/10 (2 new).
  • Baseline compared: stashed the change, rebuilt, and re-ran TabTests — the suite has 32 pre-existing failures in this environment (_initializeTerminalPage fails with 0x8000ffff). After the change: still 32 failures, passed 13 → 16. No regression.

Not covered

  • No end-to-end runtime verification — that needs a full Terminal deployment.
  • _BuildDurableTabSessionName itself has no unit test, because it needs a real TermControl. The same pre-existing _initializeTerminalPage failures block adding an integration test here. The pure helpers are well covered, but the wiring itself currently rests on code review.

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();
@github-actions

Copy link
Copy Markdown

check-spelling-bot Report

⚠️ Dictionary not found

Problems were encountered retrieving check dictionaries (cspell:cpp/src/people.txt cspell:software-terms/dict/webServices.txt cspell:docker/src/docker-words.txt cspell:sql/src/sql.txt cspell:r/src/r.txt cspell:dart/src/dart.txt cspell:swift/src/swift.txt cspell:python/src/additional_words.txt cspell:cpp/src/stdlib-c.txt cspell:lua/dict/lua.txt cspell:html/dict/html.txt cspell:python/src/python/python.txt cspell:cpp/src/compiler-clang-attributes.txt cspell:cpp/src/stdlib-cpp.txt cspell:rust/dict/rust.txt cspell:dotnet/dict/dotnet.txt cspell:cpp/src/compiler-msvc.txt cspell:npm/dict/npm.txt cspell:public-licenses/src/additional-licenses.txt cspell:cpp/src/stdlib-cerrno.txt cspell:python/src/common/extra.txt cspell:elixir/dict/elixir.txt cspell:k8s/dict/k8s.txt cspell:gaming-terms/dict/gaming-terms.txt cspell:css/dict/css.txt cspell:public-licenses/src/generated/public-licenses.txt cspell:python/src/python/python-lib.txt cspell:ruby/dict/ruby.txt cspell:powershell/dict/powershell.txt cspell:haskell/dict/haskell.txt cspell:svelte/dict/svelte.txt cspell:redis/dict/redis.txt cspell:typescript/dict/typescript.txt cspell:cpp/src/lang-jargon.txt cspell:django/dict/django.txt cspell:cpp/src/template-strings.txt cspell:node/dict/node.txt cspell:shell/dict/shell-all-words.txt cspell:java/src/java-terms.txt cspell:software-terms/dict/softwareTerms.txt cspell:cpp/src/ecosystem.txt cspell:cpp/src/compiler-gcc.txt cspell:monkeyc/src/monkeyc_keywords.txt cspell:golang/dict/go.txt cspell:cpp/src/lang-keywords.txt cspell:php/dict/php.txt cspell:fullstack/dict/fullstack.txt cspell:clojure/src/clojure.txt cspell:sql/src/tsql.txt cspell:scala/dict/scala.txt cspell:ada/dict/ada.txt cspell:cpp/src/stdlib-cmath.txt cspell:latex/dict/latex.txt cspell:java/src/java.txt).

⚠️ For more information, see check-dictionary-not-found.

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

Unrecognized words (13)
ACTIVEOBJECT
doas
dogfooder
dogfooding
gitdir
libgit
nhow
nvim
oleaut
realgit
recents
stanbul
yuazha
These words are not needed and should be removed AHP aiu Backgrounder CANTCALLOUT Ccc cplusplus ctl Debian depl dotnet drv endptr EOFs evt frob frobnicate Fullwidth gitlab hdr idl IME inbox ININPUTSYNCCALL INJ intelligentterminal Ioctl KVM lbl lld lsb NODEFAULT NONINFRINGEMENT notif OLEAUT oss outdir Podcast pri prioritization rcv segfault SND sourced SWP Tbl testname transitioning unk unparseable unregisters Virt VMs webpage websites WINVER WSLENV xsi

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:microsoft/intelligent-terminal.git repository
on the dev/yuazha/tab-auto-naming branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/intelligent-terminal/actions/runs/32786290504/attempts/1' &&
git commit -m 'Update check-spelling metadata'
Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (2101) from .github/actions/spelling/expect/alphabet.txt .github/actions/spelling/expect/expect.txt .github/actions/spelling/expect/web.txt and unrecognized words (13)

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
⚠️ check-dictionary-not-found 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.txt file 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 the patterns.txt file.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants