mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
wip
This commit is contained in:
parent
962461df32
commit
500f4132b7
@ -24,6 +24,9 @@ using namespace winrt::Windows::UI::Core;
|
||||
static const til::CoordType PaneBorderSize = 2;
|
||||
static const til::CoordType StaticMenuCount = 4; // "Separator" "Settings" "Command Palette" "About"
|
||||
|
||||
#define print_debug(s, ...) \
|
||||
OutputDebugStringW(fmt::format(FMT_COMPILE(s) __VA_OPT__(,) __VA_ARGS__).c_str());
|
||||
|
||||
static std::wstring_view split_line(std::wstring_view& remaining)
|
||||
{
|
||||
auto lf = remaining.find(L'\n');
|
||||
@ -262,9 +265,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
co_await wil::resume_foreground(_dispatcherQueue);
|
||||
|
||||
OutputDebugStringW(L"<<< ");
|
||||
OutputDebugStringW(line.c_str());
|
||||
OutputDebugStringW(L"\n");
|
||||
print_debug(L"<<< {}\n", line);
|
||||
|
||||
std::wstring_view remaining{ line };
|
||||
const auto type = tokenize_field(remaining);
|
||||
@ -299,15 +300,15 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that at this point `remaining` will not be the whole `line` anymore.
|
||||
if (_responseBuffer.empty())
|
||||
{
|
||||
// Note that at this point `remaining` will not be the whole `line` anymore.
|
||||
_responseBuffer = std::move(line);
|
||||
}
|
||||
else
|
||||
{
|
||||
_responseBuffer.append(L"\r\n");
|
||||
_responseBuffer.append(remaining);
|
||||
_responseBuffer.append(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,10 +346,14 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else if (til::equals(type, L"%window-add"))
|
||||
{
|
||||
const auto id = tokenize_identifier(remaining);
|
||||
if (id.type == IdentifierType::Window)
|
||||
// We'll handle the initial window discovery ourselves during %session-changed.
|
||||
if (_sessionId != -1)
|
||||
{
|
||||
_sendDiscoverNewWindow(id.value);
|
||||
const auto id = tokenize_identifier(remaining);
|
||||
if (id.type == IdentifierType::Window)
|
||||
{
|
||||
_sendDiscoverNewWindow(id.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (til::equals(type, L"%window-close") || til::equals(type, L"%unlinked-window-close"))
|
||||
@ -619,9 +624,6 @@ namespace winrt::TerminalApp::implementation
|
||||
case ResponseInfoType::DiscoverPanes:
|
||||
_handleResponseDiscoverPanes(response);
|
||||
break;
|
||||
case ResponseInfoType::ListWindow:
|
||||
_handleResponseListWindow(response);
|
||||
break;
|
||||
case ResponseInfoType::ListPanes:
|
||||
_handleResponseListPanes(info, response);
|
||||
break;
|
||||
@ -636,9 +638,25 @@ namespace winrt::TerminalApp::implementation
|
||||
_sendIgnoreResponse(fmt::format(FMT_COMPILE(L"set-option {}\n"), option));
|
||||
}
|
||||
|
||||
// When we join a brand new session, tmux will output:
|
||||
// %begin 1765124793 272 0
|
||||
// %end 1765124793 272 0
|
||||
// %window-add @0
|
||||
// %sessions-changed
|
||||
// %session-changed $0 0
|
||||
// %window-renamed @0 tmux
|
||||
// %output %0 ...
|
||||
// whereas if we join an existing session, we get:
|
||||
// %begin 1765125530 285 0
|
||||
// %end 1765125530 285 0
|
||||
// %session-changed $0 0
|
||||
//
|
||||
// Because of this, we have to send a `list-windows` command ourselves.
|
||||
// We do this after the `session-changed` notification, because at that point we
|
||||
// received any potential `window-add` notifications that would indicate a new session.
|
||||
void TmuxControl::_sendDiscoverWindows(int64_t sessionId)
|
||||
{
|
||||
const auto cmd = fmt::format(FMT_COMPILE(L"list-windows -F '#{{window_id}}' -t ${}\n"), sessionId);
|
||||
const auto cmd = fmt::format(FMT_COMPILE(L"list-windows -F '#{{session_id}} #{{window_id}} #{{window_width}} #{{window_height}} #{{history_limit}} #{{window_active}} #{{window_layout}} #{{window_name}}' -t ${}\n"), sessionId);
|
||||
const auto info = ResponseInfo{
|
||||
.type = ResponseInfoType::DiscoverWindows,
|
||||
};
|
||||
@ -647,26 +665,220 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_handleResponseDiscoverWindows(std::wstring_view response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseDiscoverWindows\n");
|
||||
print_debug(L"_handleResponseDiscoverWindows\n");
|
||||
|
||||
while (!response.empty())
|
||||
{
|
||||
auto line = split_line(response);
|
||||
const auto id = tokenize_identifier(line);
|
||||
if (id.type == IdentifierType::Window)
|
||||
{
|
||||
_sendResizeWindow(id.value, _terminalWidth, _terminalHeight);
|
||||
}
|
||||
else
|
||||
const auto sessionId = tokenize_identifier(line);
|
||||
const auto windowId = tokenize_identifier(line);
|
||||
const auto windowWidth = tokenize_number(line);
|
||||
const auto windowHeight = tokenize_number(line);
|
||||
const auto historyLimit = tokenize_number(line);
|
||||
const auto windowActive = tokenize_number(line);
|
||||
const auto windowLayout = tokenize_field(line);
|
||||
const auto windowName = line;
|
||||
|
||||
if (sessionId.type != IdentifierType::Session ||
|
||||
windowId.type != IdentifierType::Window ||
|
||||
!windowWidth ||
|
||||
!windowHeight ||
|
||||
!historyLimit ||
|
||||
!windowActive)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto remaining = _handleResponseDiscoverWindows_stripLayoutHash(windowLayout);
|
||||
const auto firstPane = _handleResponseDiscoverWindows_createLayoutRecursive(windowId.value, remaining, TmuxLayoutType2::Pane);
|
||||
|
||||
const auto tab = _page._GetTabImpl(_page._CreateNewTabFromPane(firstPane));
|
||||
_attachedWindows.emplace(windowId.value, tab);
|
||||
tab->SetTabText(winrt::hstring{ windowName });
|
||||
tab->CloseRequested([this, id = windowId.value](auto&&, auto&&) {
|
||||
_sendKillWindow(id);
|
||||
});
|
||||
|
||||
_sendResizeWindow(windowId.value, _terminalWidth, _terminalHeight);
|
||||
_sendListPanes(windowId.value, (til::CoordType)*historyLimit);
|
||||
}
|
||||
|
||||
_state = State::ATTACHED;
|
||||
}
|
||||
|
||||
std::shared_ptr<Pane> TmuxControl::_handleResponseDiscoverWindows_createLayoutRecursive(int64_t windowId, std::wstring_view& remaining, TmuxLayoutType2 layout)
|
||||
{
|
||||
std::shared_ptr<Pane> firstPane;
|
||||
std::shared_ptr<Pane> lastPane;
|
||||
|
||||
while (!remaining.empty())
|
||||
{
|
||||
const auto token = _handleResponseDiscoverWindows_parseNextLayoutToken(remaining);
|
||||
switch (token.type)
|
||||
{
|
||||
case TmuxLayoutType2::Pane:
|
||||
{
|
||||
float splitSize = 1;
|
||||
auto direction = SplitDirection::Right;
|
||||
switch (layout)
|
||||
{
|
||||
case TmuxLayoutType2::PushHorizontal:
|
||||
direction = SplitDirection::Right;
|
||||
//splitSize = _ComputeSplitSize(p.width, rootSize, direction);
|
||||
//rootSize -= (p.width + 1);
|
||||
break;
|
||||
case TmuxLayoutType2::PushVertical:
|
||||
direction = SplitDirection::Down;
|
||||
//splitSize = _ComputeSplitSize(p.height, rootSize, direction);
|
||||
//rootSize -= (p.height + 1);
|
||||
break;
|
||||
default:
|
||||
splitSize = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
auto pane = _newPane(windowId, token.id);
|
||||
pane->Closed([this, id = token.id](auto&&, auto&&) {
|
||||
_sendKillPane(id);
|
||||
});
|
||||
if (!firstPane)
|
||||
{
|
||||
firstPane = pane;
|
||||
}
|
||||
if (lastPane)
|
||||
{
|
||||
lastPane->AttachPane(pane, direction, splitSize);
|
||||
}
|
||||
lastPane = std::move(pane);
|
||||
break;
|
||||
}
|
||||
case TmuxLayoutType2::PushHorizontal:
|
||||
case TmuxLayoutType2::PushVertical:
|
||||
assert(false); // TODO
|
||||
//_handleResponseDiscoverWindows_createLayoutRecursive(remaining, token.type);
|
||||
break;
|
||||
case TmuxLayoutType2::Pop:
|
||||
return firstPane;
|
||||
}
|
||||
}
|
||||
|
||||
// _sendDiscoverWindows() gets called with _sessionId as its parameter.
|
||||
// We should not be in a state where it's still unset.
|
||||
assert(_sessionId != -1);
|
||||
_sendListWindow(_sessionId);
|
||||
return firstPane;
|
||||
}
|
||||
|
||||
std::wstring_view TmuxControl::_handleResponseDiscoverWindows_stripLayoutHash(std::wstring_view str)
|
||||
{
|
||||
const auto comma = str.find(L',');
|
||||
if (comma != std::wstring_view::npos)
|
||||
{
|
||||
return str.substr(comma + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Example layouts:
|
||||
// * single pane:
|
||||
// cafd,120x29,0,0,0
|
||||
// * single horizontal split:
|
||||
// 813e,120x29,0,0{60x29,0,0,0,59x29,61,0,1}
|
||||
// * double horizontal split:
|
||||
// 04d9,120x29,0,0{60x29,0,0,0,29x29,61,0,1,29x29,91,0,2}
|
||||
// * double horizontal split + single vertical split in the middle pane:
|
||||
// 773d,120x29,0,0{60x29,0,0,0,29x29,61,0[29x14,61,0,1,29x14,61,15,3],29x29,91,0,2}
|
||||
TmuxControl::TmuxPaneLayout2 TmuxControl::_handleResponseDiscoverWindows_parseNextLayoutToken(std::wstring_view& remaining)
|
||||
{
|
||||
TmuxPaneLayout2 layout{ .type = TmuxLayoutType2::Pop };
|
||||
|
||||
if (remaining.empty())
|
||||
{
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
|
||||
int64_t args[5];
|
||||
size_t arg_count = 0;
|
||||
wchar_t sep = L'\0';
|
||||
|
||||
// Collect up to 5 arguments and the final separator
|
||||
// 120x29,0,0,2, --> 120, 29, 0, 0, 2 + ','
|
||||
// 120x29,0,0{ --> 120, 29, 0, 0 + '{'
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (remaining.empty())
|
||||
{
|
||||
// Failed to collect enough args? Error.
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
|
||||
// If we're looking at a push/pop operation, break out. This is important
|
||||
// for the latter, because nested layouts may end in `]]]`, etc.
|
||||
sep = remaining[0];
|
||||
if (sep == L'[' || sep == L']' || sep == L'{' || sep == L'}')
|
||||
{
|
||||
remaining = remaining.substr(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip 1 separator. Technically we should validate their correct position here, but meh.
|
||||
if (sep == L',' || sep == L'x')
|
||||
{
|
||||
remaining = remaining.substr(1);
|
||||
// We don't need to revalidate `remaining.empty()`,
|
||||
// because parse_signed will return nullopt for empty strings.
|
||||
}
|
||||
|
||||
const auto end = std::min(remaining.size(), remaining.find_first_of(L",x[]{}"));
|
||||
const auto val = til::parse_signed<int64_t>(remaining.substr(0, end), 10);
|
||||
if (!val)
|
||||
{
|
||||
// Not an integer? Error.
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
|
||||
args[arg_count++] = *val;
|
||||
remaining = remaining.substr(end);
|
||||
}
|
||||
|
||||
switch (sep)
|
||||
{
|
||||
case L'[':
|
||||
case L'{':
|
||||
if (arg_count != 4)
|
||||
{
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
layout.type = sep == L'[' ? TmuxLayoutType2::PushVertical : TmuxLayoutType2::PushHorizontal;
|
||||
layout.width = (til::CoordType)args[0];
|
||||
layout.height = (til::CoordType)args[1];
|
||||
return layout;
|
||||
case L']':
|
||||
case L'}':
|
||||
if (arg_count != 0)
|
||||
{
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
// layout.type is already set to Pop.
|
||||
return layout;
|
||||
default:
|
||||
if (arg_count != 5)
|
||||
{
|
||||
assert(false);
|
||||
return layout;
|
||||
}
|
||||
layout.type = TmuxLayoutType2::Pane;
|
||||
layout.width = (til::CoordType)args[0];
|
||||
layout.height = (til::CoordType)args[1];
|
||||
layout.id = args[4];
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
|
||||
void TmuxControl::_sendDiscoverNewWindow(int64_t windowId)
|
||||
@ -680,7 +892,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_handleResponseDiscoverNewWindow(std::wstring_view response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseDiscoverNewWindow\n");
|
||||
print_debug(L"_handleResponseDiscoverNewWindow\n");
|
||||
|
||||
const auto windowId = tokenize_identifier(response);
|
||||
const auto paneId = tokenize_identifier(response);
|
||||
@ -707,7 +919,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_handleResponseDiscoverPanes(std::wstring_view response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseDiscoverPanes\n");
|
||||
print_debug(L"_handleResponseDiscoverPanes\n");
|
||||
|
||||
std::unordered_set<int64_t> panes;
|
||||
while (!response.empty())
|
||||
@ -739,248 +951,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TmuxControl::_sendListWindow(int64_t sessionId)
|
||||
{
|
||||
const auto cmd = fmt::format(FMT_COMPILE(L"list-windows -F '#{{session_id}} #{{window_id}} #{{window_width}} #{{window_height}} #{{history_limit}} #{{window_active}} #{{window_layout}} #{{window_name}}' -t ${}\n"), sessionId);
|
||||
const auto info = ResponseInfo{
|
||||
.type = ResponseInfoType::ListWindow,
|
||||
};
|
||||
_sendWithResponseInfo(cmd, info);
|
||||
}
|
||||
|
||||
std::vector<TmuxControl::TmuxWindowLayout> TmuxControl::_parseLayout(std::wstring_view remaining)
|
||||
{
|
||||
std::vector<TmuxWindowLayout> result;
|
||||
std::vector<size_t> stack;
|
||||
|
||||
stack.push_back(0);
|
||||
|
||||
// Example layouts:
|
||||
// * single pane:
|
||||
// cafd,120x29,0,0,0
|
||||
// * single horizontal split:
|
||||
// 813e,120x29,0,0{60x29,0,0,0,59x29,61,0,1}
|
||||
// * double horizontal split:
|
||||
// 04d9,120x29,0,0{60x29,0,0,0,29x29,61,0,1,29x29,91,0,2}
|
||||
// * double horizontal split + single vertical split in the middle pane:
|
||||
// 773d,120x29,0,0{60x29,0,0,0,29x29,61,0[29x14,61,0,1,29x14,61,15,3],29x29,91,0,2}
|
||||
remaining = L"773d,120x29,0,0{60x29,0,0,0,29x29,61,0[29x14,61,0,1,29x14,61,15,3],29x29,91,0,2}";
|
||||
|
||||
// Strip off the layout hash
|
||||
const auto comma = remaining.find(L',');
|
||||
if (comma == std::wstring_view::npos)
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
remaining = remaining.substr(comma + 1);
|
||||
|
||||
while (!remaining.empty())
|
||||
{
|
||||
int64_t args[5];
|
||||
size_t arg_count = 0;
|
||||
wchar_t sep = L'\0';
|
||||
|
||||
// Collect up to 5 arguments and the final separator
|
||||
// 120x29,0,0,2, --> 120, 29, 0, 0, 2 + ','
|
||||
// 120x29,0,0{ --> 120, 29, 0, 0 + '{'
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (remaining.empty())
|
||||
{
|
||||
// Failed to collect enough args? Error.
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
// If we're looking at a push/pop operation, break out. This is important
|
||||
// for the latter, because nested layouts may end in `]]]`, etc.
|
||||
sep = remaining[0];
|
||||
if (sep == L'[' || sep == L']' || sep == L'{' || sep == L'}')
|
||||
{
|
||||
remaining = remaining.substr(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip 1 separator. Technically we should validate their correct position here, but meh.
|
||||
if (sep == L',' || sep == L'x')
|
||||
{
|
||||
remaining = remaining.substr(1);
|
||||
// We don't need to revalidate `remaining.empty()`,
|
||||
// because parse_signed will return nullopt for empty strings.
|
||||
}
|
||||
|
||||
const auto end = std::min(remaining.size(), remaining.find_first_of(L",x[]{}"));
|
||||
const auto val = til::parse_signed<int64_t>(remaining.substr(0, end), 10);
|
||||
if (!val)
|
||||
{
|
||||
// Not an integer? Error.
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
args[arg_count++] = *val;
|
||||
remaining = remaining.substr(end);
|
||||
}
|
||||
|
||||
switch (sep)
|
||||
{
|
||||
case L'[':
|
||||
case L'{':
|
||||
if (arg_count != 4)
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
stack.push_back(result.size());
|
||||
result.push_back(TmuxWindowLayout{
|
||||
.type = sep == L'[' ? TmuxLayoutType::SPLIT_VERTICAL : TmuxLayoutType::SPLIT_HORIZONTAL,
|
||||
.width = (til::CoordType)args[0],
|
||||
.height = (til::CoordType)args[1],
|
||||
});
|
||||
break;
|
||||
case L']':
|
||||
case L'}':
|
||||
stack.pop_back();
|
||||
break;
|
||||
default:
|
||||
if (arg_count != 5)
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (result.empty())
|
||||
{
|
||||
result.push_back(TmuxWindowLayout{
|
||||
.type = TmuxLayoutType::SINGLE_PANE,
|
||||
});
|
||||
}
|
||||
|
||||
result[stack.back()].panes.push_back(TmuxPaneLayout{
|
||||
.id = args[4],
|
||||
.width = (til::CoordType)args[0],
|
||||
.height = (til::CoordType)args[1],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TmuxControl::_handleResponseListWindow(std::wstring_view response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseListWindow\n");
|
||||
|
||||
while (!response.empty())
|
||||
{
|
||||
auto line = split_line(response);
|
||||
const auto sessionId = tokenize_identifier(line);
|
||||
const auto windowId = tokenize_identifier(line);
|
||||
const auto windowWidth = tokenize_number(line);
|
||||
const auto windowHeight = tokenize_number(line);
|
||||
const auto historyLimit = tokenize_number(line);
|
||||
const auto windowActive = tokenize_number(line);
|
||||
const auto windowLayout = tokenize_field(line);
|
||||
const auto windowName = line;
|
||||
|
||||
if (sessionId.type != IdentifierType::Session ||
|
||||
windowId.type != IdentifierType::Window ||
|
||||
!windowWidth ||
|
||||
!windowHeight ||
|
||||
!historyLimit ||
|
||||
!windowActive)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto layout = _parseLayout(windowLayout);
|
||||
std::shared_ptr<Pane> rootPane{ nullptr };
|
||||
std::unordered_map<int64_t, std::shared_ptr<Pane>> attachedPanes;
|
||||
|
||||
for (auto& l : layout)
|
||||
{
|
||||
til::CoordType rootSize;
|
||||
const auto& rootPaneLayout = l.panes.at(0);
|
||||
auto direction = SplitDirection::Right;
|
||||
|
||||
switch (l.type)
|
||||
{
|
||||
case TmuxLayoutType::SINGLE_PANE:
|
||||
rootPane = _newPane(windowId.value, rootPaneLayout.id);
|
||||
continue;
|
||||
case TmuxLayoutType::SPLIT_HORIZONTAL:
|
||||
direction = SplitDirection::Right;
|
||||
rootSize = rootPaneLayout.width;
|
||||
break;
|
||||
case TmuxLayoutType::SPLIT_VERTICAL:
|
||||
direction = SplitDirection::Down;
|
||||
rootSize = rootPaneLayout.height;
|
||||
break;
|
||||
}
|
||||
|
||||
std::shared_ptr<Pane> targetPane{ nullptr };
|
||||
|
||||
if (const auto search = attachedPanes.find(rootPaneLayout.id); search == attachedPanes.end())
|
||||
{
|
||||
targetPane = _newPane(windowId.value, rootPaneLayout.id);
|
||||
if (!rootPane)
|
||||
{
|
||||
rootPane = targetPane;
|
||||
}
|
||||
attachedPanes.insert({ rootPaneLayout.id, targetPane });
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPane = search->second;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < l.panes.size(); i++)
|
||||
{
|
||||
// Create and attach
|
||||
auto& p = l.panes.at(i);
|
||||
|
||||
auto pane = _newPane(windowId.value, p.id);
|
||||
attachedPanes.insert({ p.id, pane });
|
||||
|
||||
float splitSize;
|
||||
if (direction == SplitDirection::Left)
|
||||
{
|
||||
splitSize = _ComputeSplitSize(p.width, rootSize, direction);
|
||||
rootSize -= (p.width + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
splitSize = _ComputeSplitSize(p.height, rootSize, direction);
|
||||
rootSize -= (p.height + 1);
|
||||
}
|
||||
|
||||
targetPane = targetPane->AttachPane(pane, direction, splitSize);
|
||||
attachedPanes.erase(p.id);
|
||||
attachedPanes.insert({ p.id, targetPane });
|
||||
targetPane->Closed([this, id = p.id](auto&&, auto&&) {
|
||||
_sendKillPane(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto tab = _page._GetTabImpl(_page._CreateNewTabFromPane(rootPane));
|
||||
_attachedWindows.emplace(windowId.value, tab);
|
||||
|
||||
tab->CloseRequested([this, id = windowId.value](auto&&, auto&&) {
|
||||
_sendKillWindow(id);
|
||||
});
|
||||
|
||||
tab->SetTabText(winrt::hstring{ windowName });
|
||||
_sendListPanes(windowId.value, *historyLimit);
|
||||
}
|
||||
|
||||
_state = State::ATTACHED;
|
||||
}
|
||||
|
||||
void TmuxControl::_sendListPanes(int64_t windowId, til::CoordType history)
|
||||
{
|
||||
const auto cmd = fmt::format(FMT_COMPILE(L"list-panes -F '#{{pane_id}} #{{cursor_x}} #{{cursor_y}}' -t @{}\n"), windowId);
|
||||
@ -997,7 +967,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_handleResponseListPanes(const ResponseInfo& info, std::wstring_view response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseListPanes\n");
|
||||
print_debug(L"_handleResponseListPanes\n");
|
||||
|
||||
std::vector<TmuxPane> panes;
|
||||
|
||||
@ -1050,7 +1020,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_handleResponseCapturePane(const ResponseInfo& info, std::wstring response)
|
||||
{
|
||||
OutputDebugStringW(L"_handleResponseCapturePane\n");
|
||||
print_debug(L"_handleResponseCapturePane\n");
|
||||
|
||||
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"\033[{};{}H"), info.data.capturePane.cursorY + 1, info.data.capturePane.cursorX + 1);
|
||||
_deliverOutputToPane(info.data.capturePane.paneId, response);
|
||||
@ -1213,25 +1183,19 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_deliverOutputToPane(int64_t paneId, const std::wstring_view text)
|
||||
{
|
||||
auto search = _attachedPanes.find(paneId);
|
||||
|
||||
// The pane is not ready it, put in backlog for now
|
||||
const auto search = _attachedPanes.find(paneId);
|
||||
if (search == _attachedPanes.end())
|
||||
{
|
||||
_outputBacklog.insert_or_assign(paneId, text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search->second.initialized)
|
||||
{
|
||||
search->second.control.Initialized([this, paneId, text](auto& /*i*/, auto& /*e*/) {
|
||||
_deliverOutputToPane(paneId, text);
|
||||
});
|
||||
print_debug(L"--> outputBacklog {}\n", paneId);
|
||||
search->second.outputBacklog.append(text);
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDebugStringW(fmt::format(FMT_COMPILE(L"_deliverOutputToPane({}, {})\n"), paneId, text).c_str());
|
||||
|
||||
std::wstring out;
|
||||
auto it = text.begin();
|
||||
const auto end = text.end();
|
||||
@ -1269,6 +1233,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
print_debug(L"--> _deliverOutputToPane {}\n", paneId);
|
||||
search->second.connection.WriteOutput(winrt_wstring_to_array_view(out));
|
||||
}
|
||||
|
||||
@ -1283,16 +1248,6 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
|
||||
tab->SetTabText(winrt::hstring{ windowName });
|
||||
|
||||
// Check if we have output before we are ready
|
||||
auto search = _outputBacklog.find(paneId);
|
||||
if (search == _outputBacklog.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_deliverOutputToPane(paneId, search->second);
|
||||
_outputBacklog.erase(search);
|
||||
}
|
||||
|
||||
std::shared_ptr<Pane> TmuxControl::_newPane(int64_t windowId, int64_t paneId)
|
||||
@ -1305,12 +1260,17 @@ namespace winrt::TerminalApp::implementation
|
||||
auto pane = std::make_shared<Pane>(paneContent);
|
||||
|
||||
control.Initialized([this, paneId](auto, auto) {
|
||||
auto search = _attachedPanes.find(paneId);
|
||||
const auto search = _attachedPanes.find(paneId);
|
||||
if (search == _attachedPanes.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
search->second.initialized = true;
|
||||
if (!search->second.outputBacklog.empty())
|
||||
{
|
||||
_deliverOutputToPane(paneId, std::move(search->second.outputBacklog));
|
||||
search->second.outputBacklog.clear();
|
||||
}
|
||||
});
|
||||
|
||||
connection.TerminalInput([this, paneId](const winrt::array_view<const char16_t> keys) {
|
||||
@ -1361,8 +1321,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_sendIgnoreResponse(wil::zwstring_view cmd)
|
||||
{
|
||||
OutputDebugStringW(L">>> ");
|
||||
OutputDebugStringW(cmd.c_str());
|
||||
print_debug(L">>> {}", cmd);
|
||||
|
||||
_control.RawWriteString(cmd);
|
||||
_commandQueue.push_back(ResponseInfo{
|
||||
@ -1372,8 +1331,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TmuxControl::_sendWithResponseInfo(wil::zwstring_view cmd, ResponseInfo info)
|
||||
{
|
||||
OutputDebugStringW(L">>> ");
|
||||
OutputDebugStringW(cmd.c_str());
|
||||
print_debug(L">>> {}", cmd);
|
||||
|
||||
_control.RawWriteString(cmd);
|
||||
_commandQueue.push_back(info);
|
||||
|
||||
@ -38,7 +38,6 @@ namespace winrt::TerminalApp::implementation
|
||||
DiscoverNewWindow,
|
||||
DiscoverWindows,
|
||||
DiscoverPanes,
|
||||
ListWindow,
|
||||
ListPanes,
|
||||
CapturePane,
|
||||
};
|
||||
@ -71,29 +70,48 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
struct TmuxPaneLayout
|
||||
{
|
||||
int64_t id;
|
||||
til::CoordType width;
|
||||
til::CoordType height;
|
||||
TmuxLayoutType type = TmuxLayoutType::SINGLE_PANE;
|
||||
til::CoordType width = 0;
|
||||
til::CoordType height = 0;
|
||||
// Individual panes (= SINGLE_PANE) have an ID,
|
||||
// otherwise it's unset (-1).
|
||||
int64_t id = -1;
|
||||
// Individual panes imply `panes.empty()`.
|
||||
std::vector<TmuxPaneLayout> panes;
|
||||
};
|
||||
|
||||
struct TmuxWindowLayout
|
||||
enum class TmuxLayoutType2
|
||||
{
|
||||
TmuxLayoutType type = TmuxLayoutType::SINGLE_PANE;
|
||||
til::CoordType width;
|
||||
til::CoordType height;
|
||||
std::vector<TmuxPaneLayout> panes;
|
||||
// A single leaf pane
|
||||
Pane,
|
||||
// Indicates the start of a horizontal split layout
|
||||
PushHorizontal,
|
||||
// Indicates the start of a vertical split layout
|
||||
PushVertical,
|
||||
// Indicates the end of the most recent split layout
|
||||
Pop,
|
||||
};
|
||||
|
||||
struct TmuxPaneLayout2
|
||||
{
|
||||
TmuxLayoutType2 type = TmuxLayoutType2::Pane;
|
||||
|
||||
// Only set for: Pane, PushHorizontal, PushVertical
|
||||
til::CoordType width = 0;
|
||||
// Only set for: Pane, PushHorizontal, PushVertical
|
||||
til::CoordType height = 0;
|
||||
// Only set for: Pane
|
||||
int64_t id = -1;
|
||||
};
|
||||
|
||||
struct TmuxWindow
|
||||
{
|
||||
int64_t sessionId = 0;
|
||||
int64_t windowId = 0;
|
||||
til::CoordType width = 0;
|
||||
til::CoordType height = 0;
|
||||
til::CoordType history = 0;
|
||||
bool active = false;
|
||||
std::wstring name;
|
||||
std::vector<TmuxWindowLayout> layout;
|
||||
TmuxPaneLayout layout;
|
||||
};
|
||||
|
||||
struct TmuxPane
|
||||
@ -112,11 +130,10 @@ namespace winrt::TerminalApp::implementation
|
||||
int64_t paneId = 0;
|
||||
winrt::Microsoft::Terminal::Control::TermControl control;
|
||||
winrt::Microsoft::Terminal::TerminalConnection::TmuxConnection connection;
|
||||
std::wstring outputBacklog;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
static std::vector<TmuxControl::TmuxWindowLayout> _parseLayout(std::wstring_view remaining);
|
||||
static std::vector<TmuxControl::TmuxWindowLayout> _ParseTmuxWindowLayout(std::wstring layout);
|
||||
|
||||
safe_void_coroutine _parseLine(std::wstring str);
|
||||
|
||||
@ -131,12 +148,13 @@ namespace winrt::TerminalApp::implementation
|
||||
void _sendSetOption(std::wstring_view option);
|
||||
void _sendDiscoverWindows(int64_t sessionId);
|
||||
void _handleResponseDiscoverWindows(std::wstring_view response);
|
||||
std::shared_ptr<Pane> _handleResponseDiscoverWindows_createLayoutRecursive(int64_t windowId, std::wstring_view& remaining, TmuxLayoutType2 layout);
|
||||
std::wstring_view _handleResponseDiscoverWindows_stripLayoutHash(std::wstring_view str);
|
||||
TmuxPaneLayout2 _handleResponseDiscoverWindows_parseNextLayoutToken(std::wstring_view& remaining);
|
||||
void _sendDiscoverNewWindow(int64_t windowId);
|
||||
void _handleResponseDiscoverNewWindow(std::wstring_view response);
|
||||
void _sendDiscoverPanes(int64_t sessionId);
|
||||
void _handleResponseDiscoverPanes(std::wstring_view response);
|
||||
void _sendListWindow(int64_t sessionId);
|
||||
void _handleResponseListWindow(std::wstring_view response);
|
||||
void _sendListPanes(int64_t windowId, til::CoordType history);
|
||||
void _handleResponseListPanes(const ResponseInfo& info, std::wstring_view response);
|
||||
void _sendCapturePane(int64_t paneId, til::CoordType cursorX, til::CoordType cursorY, til::CoordType history);
|
||||
@ -183,7 +201,6 @@ namespace winrt::TerminalApp::implementation
|
||||
std::deque<ResponseInfo> _commandQueue;
|
||||
std::unordered_map<int64_t, AttachedPane> _attachedPanes;
|
||||
std::unordered_map<int64_t, winrt::com_ptr<Tab>> _attachedWindows;
|
||||
std::unordered_map<int64_t, std::wstring> _outputBacklog;
|
||||
|
||||
int64_t _sessionId = -1;
|
||||
int64_t _activePaneId = -1;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user