Skip to content

Commit aec38b9

Browse files
Merge pull request #1689 from contour-terminal/improvement/tab-switching
[contour] Improve tab switch handling and add `SwitchToPreviousTab`
2 parents f0ef26a + 0b85979 commit aec38b9

8 files changed

+130
-83
lines changed

metainfo.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
<li>Fixes startup crash when window is not yet fully initialized</li>
114114
<li>Fixes backtab (Shift+Tab) handling (#1685)</li>
115115
<li>Fixes various spelling typos across the codebase (#1688)</li>
116+
<li>Improve tab close handling to better select previously focused tab</li>
117+
<li>Add action `SwitchToPreviousTab` to switch to the previously focused tab</li>
116118
</ul>
117119
</description>
118120
</release>

src/contour/Actions.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ optional<Action> fromString(string const& name)
8282
mapAction<actions::CreateNewTab>("CreateNewTab"),
8383
mapAction<actions::CloseTab>("CloseTab"),
8484
mapAction<actions::SwitchToTab>("SwitchToTab"),
85+
mapAction<actions::SwitchToPreviousTab>("SwitchToPreviousTab"),
8586
mapAction<actions::SwitchToTabLeft>("SwitchToTabLeft"),
8687
mapAction<actions::SwitchToTabRight>("SwitchToTabRight"),
8788
};

src/contour/Actions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ struct WriteScreen{ std::string chars; }; // "\033[2J\033[3J"
8282
struct CreateNewTab{};
8383
struct CloseTab{};
8484
struct SwitchToTab{ int position; };
85+
struct SwitchToPreviousTab{};
8586
struct SwitchToTabLeft{};
8687
struct SwitchToTabRight{};
8788
// clang-format on
@@ -140,6 +141,7 @@ using Action = std::variant<CancelSelection,
140141
CreateNewTab,
141142
CloseTab,
142143
SwitchToTab,
144+
SwitchToPreviousTab,
143145
SwitchToTabLeft,
144146
SwitchToTabRight>;
145147

@@ -260,6 +262,7 @@ namespace documentation
260262
constexpr inline std::string_view SwitchToTab {
261263
"Switch to absolute tab position (starting at number 1)"
262264
};
265+
constexpr inline std::string_view SwitchToPreviousTab { "Switch to the previously focused tab" };
263266
constexpr inline std::string_view SwitchToTabLeft { "Switch to tab to the left" };
264267
constexpr inline std::string_view SwitchToTabRight { "Switch to tab to the right" };
265268
} // namespace documentation
@@ -321,6 +324,7 @@ inline auto getDocumentation()
321324
std::tuple { Action { CreateNewTab {} }, documentation::CreateNewTab },
322325
std::tuple { Action { CloseTab {} }, documentation::CloseTab },
323326
std::tuple { Action { SwitchToTab {} }, documentation::SwitchToTab },
327+
std::tuple { Action { SwitchToPreviousTab {} }, documentation::SwitchToPreviousTab },
324328
std::tuple { Action { SwitchToTabLeft {} }, documentation::SwitchToTabLeft },
325329
std::tuple { Action { SwitchToTabRight {} }, documentation::SwitchToTabRight },
326330
};
@@ -393,6 +397,7 @@ DECLARE_ACTION_FMT(ViNormalMode)
393397
DECLARE_ACTION_FMT(WriteScreen)
394398
DECLARE_ACTION_FMT(CreateNewTab)
395399
DECLARE_ACTION_FMT(CloseTab)
400+
DECLARE_ACTION_FMT(SwitchToPreviousTab)
396401
DECLARE_ACTION_FMT(SwitchToTabLeft)
397402
DECLARE_ACTION_FMT(SwitchToTabRight)
398403
// }}}
@@ -471,6 +476,7 @@ struct std::formatter<contour::actions::Action>: std::formatter<std::string>
471476
HANDLE_ACTION(ViNormalMode);
472477
HANDLE_ACTION(CreateNewTab);
473478
HANDLE_ACTION(CloseTab);
479+
HANDLE_ACTION(SwitchToPreviousTab);
474480
HANDLE_ACTION(SwitchToTabLeft);
475481
HANDLE_ACTION(SwitchToTabRight);
476482
if (std::holds_alternative<contour::actions::SwitchToTab>(_action))

src/contour/TerminalSession.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,12 @@ bool TerminalSession::operator()(actions::SwitchToTab const& event)
14461446
return true;
14471447
}
14481448

1449+
bool TerminalSession::operator()(actions::SwitchToPreviousTab)
1450+
{
1451+
emit switchToPreviousTab();
1452+
return true;
1453+
}
1454+
14491455
bool TerminalSession::operator()(actions::SwitchToTabLeft)
14501456
{
14511457
emit switchToTabLeft();

src/contour/TerminalSession.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
357357
bool operator()(actions::CreateNewTab);
358358
bool operator()(actions::CloseTab);
359359
bool operator()(actions::SwitchToTab const& event);
360+
bool operator()(actions::SwitchToPreviousTab);
360361
bool operator()(actions::SwitchToTabLeft);
361362
bool operator()(actions::SwitchToTabRight);
362363

@@ -406,6 +407,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
406407
// Tab handling signals
407408
void createNewTab();
408409
void closeTab();
410+
void switchToPreviousTab();
409411
void switchToTabLeft();
410412
void switchToTabRight();
411413
void switchToTab(int position);

src/contour/TerminalSessionManager.cpp

Lines changed: 90 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
#include <algorithm>
1616
#include <filesystem>
17-
#include <mutex>
1817
#include <string>
1918

2019
using namespace std::string_literals;
@@ -44,10 +43,16 @@ std::unique_ptr<vtpty::Pty> TerminalSessionManager::createPty(std::optional<std:
4443
}
4544

4645
TerminalSession* TerminalSessionManager::createSession()
46+
{
47+
return activateSession(createSessionInBackground());
48+
}
49+
50+
TerminalSession* TerminalSessionManager::createSessionInBackground()
4751
{
4852
// TODO: Remove dependency on app-knowledge and pass shell / terminal-size instead.
4953
// The GuiApp *or* (Global)Config could be made a global to be accessable from within QML.
50-
//
54+
55+
_previousActiveSession = _activeSession;
5156

5257
#if !defined(_WIN32)
5358
auto ptyPath = [this]() -> std::optional<std::string> {
@@ -72,7 +77,7 @@ TerminalSession* TerminalSessionManager::createSession()
7277
#endif
7378

7479
auto* session = new TerminalSession(createPty(ptyPath), _app);
75-
managerLog()("CREATE SESSION, new session: {}", (void*) session);
80+
managerLog()("Create new session with ID {} at index {}", session->id(), _sessions.size());
7681

7782
_sessions.push_back(session);
7883

@@ -84,55 +89,81 @@ TerminalSession* TerminalSessionManager::createSession()
8489
// sessions. This will work around it, by explicitly claiming ownership of the object.
8590
QQmlEngine::setObjectOwnership(session, QQmlEngine::CppOwnership);
8691

87-
// we can close application right after session has been created
88-
_lastTabChange = std::chrono::steady_clock::now() - std::chrono::seconds(1);
89-
_activeSession = session;
9092
return session;
9193
}
9294

9395
void TerminalSessionManager::setSession(size_t index)
9496
{
9597
Require(index <= _sessions.size());
9698
managerLog()(std::format("SET SESSION: index: {}, _sessions.size(): {}", index, _sessions.size()));
99+
97100
if (!isAllowedToChangeTabs())
98101
return;
99102

100-
Require(display != nullptr);
101-
auto const pixels = display->pixelSize();
102-
auto const totalPageSize = display->calculatePageSize() + _activeSession->terminal().statusLineHeight();
103+
if (index < _sessions.size())
104+
activateSession(_sessions[index]);
105+
else
106+
activateSession(createSessionInBackground());
107+
}
103108

104-
auto* oldSession = _activeSession;
109+
TerminalSession* TerminalSessionManager::activateSession(TerminalSession* session, bool isNewSession)
110+
{
111+
managerLog()(
112+
"Activating session ID {} at index {}", session->id(), getSessionIndexOf(session).value_or(-1));
105113

106-
if (index < _sessions.size())
114+
if (_activeSession == session)
107115
{
108-
_activeSession = _sessions[index];
109-
// Ensure that the existing session is resized to the display's size.
110-
_activeSession->terminal().resizeScreen(totalPageSize, pixels);
116+
managerLog()("Session is already active. (index {}, ID {})", getCurrentSessionIndex(), session->id());
117+
return session;
111118
}
112-
else
113-
createSession();
114-
115-
if (oldSession == _activeSession)
116-
return;
117119

118-
display->setSession(_activeSession);
119-
// Resize active session after display is attached to it
120-
// to return a lost line
121-
_activeSession->terminal().resizeScreen(totalPageSize, pixels);
120+
_previousActiveSession = _activeSession;
121+
_activeSession = session;
122+
_lastTabChange = std::chrono::steady_clock::now();
122123
updateStatusLine();
123124

124-
_lastTabChange = std::chrono::steady_clock::now();
125+
if (display)
126+
{
127+
managerLog()("Attaching display to session.");
128+
auto const pixels = display->pixelSize();
129+
auto const totalPageSize =
130+
display->calculatePageSize() + _previousActiveSession->terminal().statusLineHeight();
131+
132+
// Ensure that the existing session is resized to the display's size.
133+
if (!isNewSession)
134+
_activeSession->terminal().resizeScreen(totalPageSize, pixels);
135+
136+
display->setSession(_activeSession);
137+
138+
// Resize active session after display is attached to it
139+
// to return a lost line
140+
_activeSession->terminal().resizeScreen(totalPageSize, pixels);
141+
}
142+
143+
return session;
125144
}
126145

127146
void TerminalSessionManager::addSession()
128147
{
129-
setSession(_sessions.size());
148+
activateSession(createSessionInBackground(), true /*force resize on before display-attach*/);
149+
}
150+
151+
void TerminalSessionManager::switchToPreviousTab()
152+
{
153+
managerLog()("switch to previous tab (current: {}, previous: {})",
154+
getSessionIndexOf(_activeSession).value_or(-1),
155+
getSessionIndexOf(_previousActiveSession).value_or(-1));
156+
157+
if (!isAllowedToChangeTabs())
158+
return;
159+
160+
activateSession(_previousActiveSession);
130161
}
131162

132163
void TerminalSessionManager::switchToTabLeft()
133164
{
134165
const auto currentSessionIndex = getCurrentSessionIndex();
135-
managerLog()(std::format("PREVIOUS TAB: currentSessionIndex: {}, _sessions.size(): {}",
166+
managerLog()(std::format("previous tab: currentSessionIndex: {}, _sessions.size(): {}",
136167
currentSessionIndex,
137168
_sessions.size()));
138169

@@ -164,73 +195,59 @@ void TerminalSessionManager::switchToTabRight()
164195

165196
void TerminalSessionManager::switchToTab(int position)
166197
{
167-
managerLog()(std::format(
168-
"switchToTab from {} to {} (out of {})", getCurrentSessionIndex(), position - 1, _sessions.size()));
198+
managerLog()("switchToTab from index {} to {} (out of {})",
199+
getSessionIndexOf(_activeSession).value_or(-1),
200+
position - 1,
201+
_sessions.size());
202+
203+
if (!isAllowedToChangeTabs())
204+
return;
169205

170206
if (1 <= position && position <= static_cast<int>(_sessions.size()))
171-
{
172-
setSession(position - 1);
173-
}
207+
activateSession(_sessions[position - 1]);
174208
}
175209

176210
void TerminalSessionManager::closeTab()
177211
{
178-
const auto currentSessionIndex = getCurrentSessionIndex();
179-
managerLog()(std::format(
180-
"CLOSE TAB: currentSessionIndex: {}, _sessions.size(): {}", currentSessionIndex, _sessions.size()));
181-
182-
// Session was removed outside of terminal session manager, we need to switch to another tab.
183-
if (currentSessionIndex == -1 && !_sessions.empty())
184-
{
185-
// We need to switch to another tab, so we permit consequent tab changes.
186-
// TODO: This is a bit hacky.
187-
_lastTabChange = std::chrono::steady_clock::now() - std::chrono::seconds(1);
188-
setSession(0);
189-
return;
190-
}
191-
192-
if (_sessions.size() > 1)
193-
{
212+
managerLog()("Close tab: current session ID {}, index {}",
213+
getSessionIndexOf(_activeSession).value_or(-1),
214+
_activeSession->id());
194215

195-
if (!isAllowedToChangeTabs())
196-
return;
197-
198-
removeSession(*_activeSession);
199-
200-
// We need to switch to another tab, so we permit consequent tab changes.
201-
// TODO: This is a bit hacky.
202-
_lastTabChange = std::chrono::steady_clock::now() - std::chrono::seconds(1);
203-
204-
if (std::cmp_less_equal(currentSessionIndex, _sessions.size() - 1))
205-
{
206-
setSession(currentSessionIndex + 1);
207-
}
208-
else
209-
{
210-
setSession(currentSessionIndex - 1);
211-
}
212-
}
216+
removeSession(*_activeSession);
213217
}
214218

215219
void TerminalSessionManager::removeSession(TerminalSession& thatSession)
216220
{
217-
managerLog()(std::format(
218-
"REMOVE SESSION: session: {}, _sessions.size(): {}", (void*) &thatSession, _sessions.size()));
221+
managerLog()("REMOVE SESSION: session: {}, _sessions.size(): {}", (void*) &thatSession, _sessions.size());
219222

220223
if (!isAllowedToChangeTabs())
221224
return;
222225

223-
_app.onExit(thatSession); // TODO: the logic behind that impl could probably be moved here.
226+
if (&thatSession == _activeSession && _previousActiveSession)
227+
activateSession(_previousActiveSession);
224228

225-
auto i = std::ranges::find_if(_sessions, [&](auto p) { return p == &thatSession; });
226-
if (i != _sessions.end())
229+
auto i = std::ranges::find(_sessions, &thatSession);
230+
if (i == _sessions.end())
227231
{
228-
_sessions.erase(i);
232+
managerLog()("Session not found in session list.");
233+
return;
229234
}
235+
_sessions.erase(i);
236+
_app.onExit(thatSession); // TODO: the logic behind that impl could probably be moved here.
237+
238+
_previousActiveSession = [&]() -> TerminalSession* {
239+
auto const currentIndex = getSessionIndexOf(_activeSession).value_or(0);
240+
if (currentIndex + 1 < _sessions.size())
241+
return _sessions[currentIndex + 1];
242+
else if (currentIndex > 0)
243+
return _sessions[currentIndex - 1];
244+
else
245+
return nullptr;
246+
}();
247+
managerLog()("Calculated next \"previous\" session index {}",
248+
getSessionIndexOf(_previousActiveSession).value_or(-1));
230249

231250
updateStatusLine();
232-
_lastTabChange = std::chrono::steady_clock::now();
233-
// Notify app if all sessions have been killed to trigger app termination.
234251
}
235252

236253
void TerminalSessionManager::updateColorPreference(vtbackend::ColorPreference const& preference)

0 commit comments

Comments
 (0)