Skip to content

Conversation

@joexue
Copy link

@joexue joexue commented May 20, 2025

Summary of the Pull Request

Let WT supports tmux control mode

References and Relevant Issues

#3656

Detailed Description of the Pull Request / Additional comments

Screenshot 2025-05-13 232209

Support:

  • Create/attach tmux session

  • Split pane vertical/horizontal

  • Window/panes size change

  • Remove pane/tab if remote pane exit or window exit

Improvements may do:

  • Merge the tmux menus/button with existing menu/button
  • Add the keyboard accelerators for tmux menus
  • Unicode support(the VTParser just pass part of characters in DCS mode
  • When attached a session, focus on the same focused tab/pane when detaching(backend is done, need to do some work frontend)
  • Test code

Tested by using tmux 3.4:
ssh to a machine has tmux or use wsl, then run
"tmux -CC" or "tmux -CC a"

Validation Steps Performed

PR Checklist

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Area-Interop Communication between processes Area-UserInterface Issues pertaining to the user interface of the Console or Terminal Product-Terminal The new Windows Terminal. labels May 20, 2025
@joexue

This comment was marked as outdated.

@github-actions

This comment has been minimized.

@joexue

This comment was marked as outdated.

@joexue

This comment was marked as outdated.

@joexue
Copy link
Author

joexue commented May 20, 2025

@microsoft-github-policy-service agree

@DHowett
Copy link
Member

DHowett commented May 20, 2025

whoa

@iDarshan
Copy link

This is a long-awaited feature! Thanks @joexue!

@BinaryShrub
Copy link

gif

@DHowett
Copy link
Member

DHowett commented May 23, 2025

FYI: I am working with reduced staffing at the moment, so reviews may be slow to come. I apologize for that.

@iDarshan
Copy link

iDarshan commented Jun 3, 2025

Is there a nightly build with this PR to test?

@carlos-zamora
Copy link
Member

Is there a nightly build with this PR to test?

Your best bet is to build this locally. Once it merges, it will be available on the Canary channel though.

@iDarshan
Copy link

iDarshan commented Jun 6, 2025

Is there a nightly build with this PR to test?

Your best bet is to build this locally. Once it merges, it will be available on the Canary channel though.

I tried to find the build guide, but couldn't find one. Do you have the link handy?

@joexue
Copy link
Author

joexue commented Jun 6, 2025

Is there a nightly build with this PR to test?

Your best bet is to build this locally. Once it merges, it will be available on the Canary channel though.

I tried to find the build guide, but couldn't find one. Do you have the link handy?

README.md ->Developer Guidance @iDarshan

@carlos-zamora
Copy link
Member

Hi @joexue! Thanks for doing this! I published a build for the team to test internally and we've got a few notes we want to share with you 😊

  • Notes/Reactions:
    • Settings menu item is gone from the dropdown
    • It makes your existing window a special "tmux only" window; it disables the menu, but it doesn't disable the key bindings, so you can still split in non-tmux panes
    • Scrollbars are gone? I guess scrolling is server side
  • Requested changes:
    • [Bug] can't type in an older pane after splitting panes
    • tmux tabs should not be draggable into a new window
    • Add a setting to Profile > "Terminal Emulation" page that disables this (it should be disabled by default)
    • Bug: Closing a tab/pane will not kill the instance on the server side
    • Add a feature flag to features.xml (Guidance)
    • [Architectural notes] We should see if we can't just model a tmux pane as a connection so we don't need to pump output into the terminal from the App layer

Overall, this is a really exciting feature and we're excited to see it land! Let us know if you need any guidance or additional comments on any of the thoughts above. 😊

CC @DHowett @lhecker @zadjii-msft

@joexue
Copy link
Author

joexue commented Jun 12, 2025

Hi @joexue! Thanks for doing this! I published a build for the team to test internally and we've got a few notes we want to share with you 😊

  • Notes/Reactions:

    • Settings menu item is gone from the dropdown
    • It makes your existing window a special "tmux only" window; it disables the menu, but it doesn't disable the key bindings, so you can still split in non-tmux panes
    • Scrollbars are gone? I guess scrolling is server side

Scrollbars are gone is by design, I put the comment in the code, since we want to make local panes size match remote(tmux) panes size, but each split, tmux just lost 1 character to use it as separator, if we have side scrollbars, we cannot make two side's size match. To meet this, the padding size is changed too.

As the menu, it is because I tried to touch as less code as I can to do the job. So give the tmux a separate UI system. I described in the PR, this could be improved and should integrated into the present UI/menu system.
But, I think we can skip this ugly stage, directly change the code to make the tmux control mode work with present UI/menu system. I will address this, those +button/flyout splitting menus specifically for tmux will be gone.

  • Requested changes:

    • [Bug] can't type in an older pane after splitting panes

I did not see this bug, to help me to reproduce it here, could you describe your env? how do you connect to tmux server? what is the tmux version?

  • tmux tabs should not be draggable into a new window

Sure, let me fix it.

  • Add a setting to Profile > "Terminal Emulation" page that disables this (it should be disabled by default)

Sure, let me look it.

  • Bug: Closing a tab/pane will not kill the instance on the server side

This is by design, remote pane should close by quit it's app. but this is arguable, if we think close local pane should close remote pane too, it is easy and feasible.

  • Add a feature flag to features.xml (Guidance)

Sure. Fair enough.

  • [Architectural notes] We should see if we can't just model a tmux pane as a connection so we don't need to pump output into the terminal from the App layer

Please provide the idea, I can try to implement it.

Overall, this is a really exciting feature and we're excited to see it land! Let us know if you need any guidance or additional comments on any of the thoughts above. 😊

CC @DHowett @lhecker @zadjii-msft

Thanks!

Since this PR is a bit big, another option is we split it into two or three parts to make it easy to review and integrate. Such as
part 1: infra libs(parser, TermControl, Terminal, ControlCore)
part 2: TmuxControl
part 3: integrating and UI/Menu

What do you think, split it or just keep as it is?

@joexue
Copy link
Author

joexue commented Jun 17, 2025

@carlos-zamora @DHowett @lhecker @zadjii-msft
All comments are addressed, please try again.

Thanks

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@joexue joexue force-pushed the tmux_cc_pr branch 2 times, most recently from 54c2bac to 4817698 Compare June 20, 2025 01:35
@joexue joexue force-pushed the tmux_cc_pr branch 2 times, most recently from 9b798b3 to eefdfd8 Compare June 24, 2025 02:26
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@joexue joexue force-pushed the tmux_cc_pr branch 2 times, most recently from 821dbf8 to 69260b3 Compare July 6, 2025 18:45
@github-actions

This comment has been minimized.

@joexue
Copy link
Author

joexue commented Jul 23, 2025

Anything we can do on this?

@DHowett
Copy link
Member

DHowett commented Jul 29, 2025

Anything we can do on this?

We're just about to release v1.24, and then we can really start to go crazy with new features 🙂

@joexue
Copy link
Author

joexue commented Aug 1, 2025

Anything we can do on this?

We're just about to release v1.24, and then we can really start to go crazy with new features 🙂

Understood, take your time.👌

@iDarshan
Copy link

iDarshan commented Oct 2, 2025

Anything we can do on this?

We're just about to release v1.24, and then we can really start to go crazy with new features 🙂

Any updates on getting this merged?

@bilfinger-fellner-h
Copy link

What's holding this up at the moment? I've anxiously been sitting on needles waiting for tmux control mode support in Windows Terminal for quite a while now.

@lhecker
Copy link
Member

lhecker commented Dec 5, 2025

I began working on this PR late last week. It has a few issues that require fundamental changes to the PR. For instance, we use the ITerminalApi interface to raise notifications through our stack. We also shouldn't do any IO on the UI thread (which this PR relies on). But the good news (well, hopefully good news 😅) is that I'm taking care of all that. 🙂

@joexue
Copy link
Author

joexue commented Dec 7, 2025

I began working on this PR late last week. It has a few issues that require fundamental changes to the PR. For instance, we use the ITerminalApi interface to raise notifications through our stack. We also shouldn't do any IO on the UI thread (which this PR relies on). But the good news (well, hopefully good news 😅) is that I'm taking care of all that. 🙂

Definitely good news, thanks for taking care of that

Comment on lines +3395 to +3397
control.SetTmuxControlHandlerProducer([this, control](auto print) {
return _tmuxControl->TmuxControlHandlerProducer(control, print);
});
Copy link
Member

Choose a reason for hiding this comment

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

I'm going to leave some comments on your PR now for things that I found and already corrected in my branch. The problem is that some of the required changes essentially break your PR fundamentally, and I found it difficult to piece it back together. That's why I haven't committed it yet. 😅

In any case, this first one is pretty gnarly. Because your lambda here takes a copy of control and then you store the lambda on control itself, you're leaking the control instance. It'll never be freed.

(It's not possible to fix this by passing control as a weak pointer because this breaks the Close events. I'm still working to fix this.)

const std::wregex TmuxControl::REG_WINDOW_ADD{ L"^%window-add @(\\d+)$" };
const std::wregex TmuxControl::REG_WINDOW_CLOSE{ L"^%window-close @(\\d+)$" };
const std::wregex TmuxControl::REG_WINDOW_PANE_CHANGED{ L"^%window-pane-changed @(\\d+) %(\\d+)$" };
const std::wregex TmuxControl::REG_WINDOW_RENAMED{ L"^%window-renamed @(\\d+) (\\S+)$" };
Copy link
Member

Choose a reason for hiding this comment

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

While regexes are fine for some things, I'm personally not happy with the reliance on them in this PR. I believe we should write proper, classic parsers for the tmux protocol.

// Tmux control has its own profile, we duplicate it from the control panel
void TmuxControl::_SetupProfile()
{
const auto settings{ CascadiaSettings::LoadDefaults() };
Copy link
Member

@lhecker lhecker Dec 10, 2025

Choose a reason for hiding this comment

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

FWIW, creating an entire settings object for cloning the profile is a bit ugly. I have not changed this however, as we indeed lack the facilities to do it differently.

Comment on lines +400 to +401
out.push_back(c);
++it;
Copy link
Member

Choose a reason for hiding this comment

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

For ideal performance this should do a chunk-wise translation of the output payload.

// The pane is not ready it, put int backlog for now
if (search == _attachedPanes.end())
{
_outputBacklog.insert_or_assign(paneId, text);
Copy link
Member

Choose a reason for hiding this comment

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

If we create an _attachedPanes item in advance here, we can store the output backlog in the AttachedPane struct. I think this also makes some other code a bit neater.

Comment on lines +18 to +27
void DummyConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
{
const auto data = winrt_array_to_wstring_view(buffer);
std::wstringstream prettyPrint;
for (const auto& wch : data)
{
prettyPrint << wch;
}
TerminalInput.raise(prettyPrint.str());
}
Copy link
Member

Choose a reason for hiding this comment

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

This isn't quite ideal. To fix this we can simply change TerminalInput to also send Char[] instead of String around. Then this can simple pass the buffer through unmodified.


namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
DummyConnection::DummyConnection() noexcept = default;
Copy link
Member

Choose a reason for hiding this comment

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

Instead of making this a dummy connection, this should ideally be a genuine TmuxConnection which raises proper output events. All we need to do for that is save the connection instance in TmuxControl and use it to raise the output events.

Comment on lines +481 to +490
void ControlCore::SendOutput(const std::wstring_view wstr)
{
if (wstr.empty())
{
return;
}

auto lock = _terminal->LockForWriting();
_terminal->Write(wstr);
}
Copy link
Member

Choose a reason for hiding this comment

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

Having a TmuxConnection then makes this unnecessary.

Comment on lines +4811 to +4812
PrintString(s);
_DoLineFeed(page, true, true);
Copy link
Member

Choose a reason for hiding this comment

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

While the PrintHandler is a very clever solution, I don't think that we should do this. It taints the VT parser with even more Windows-Terminal-specific code. It moves us further into a "big ball of mud" direction instead of the opposite of turning our VT parser into a purer, proper parser. I've broken this functionality in my local branch, and I hope we can restore this functionality at some point later.

Comment on lines +4819 to +4822
void AdaptDispatch::SetTmuxControlHandlerProducer(StringHandlerProducer producer)
{
_tmuxControlHandlerProducer = producer;
}
Copy link
Member

Choose a reason for hiding this comment

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

We use the ITerminalApi interface to raise notifications to the hosting terminal (a typical push parser design). If we now have callback functions on top of that, this would make it worse. The correct approach is to add a ITerminalApi function which returns a StringHandler handler.

}

// From tmux to controller through the dcs. parse it per line.
bool TmuxControl::_Advance(wchar_t ch)
Copy link
Member

Choose a reason for hiding this comment

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

I've written #19640 to improve the performance of this PR. I've already modified this function in my branch to process entire std::wstring_views at a time.

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

Labels

Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Area-Interop Communication between processes Area-UserInterface Issues pertaining to the user interface of the Console or Terminal Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.

7 participants