diff --git a/metainfo.xml b/metainfo.xml
index d3246cac9a..50b03ada8c 100644
--- a/metainfo.xml
+++ b/metainfo.xml
@@ -113,6 +113,7 @@
Adds `MoveTabTo` action to move tabs to a specific position (#1695)
Adds handling of control codes for Ctrl+5|6|7|8 (#1701)
Adds CenterCursor (`zz`) vi motion
+ Adds ability to name tabs (#1690)
diff --git a/src/contour/Actions.cpp b/src/contour/Actions.cpp
index 7fb44a673e..c3e2890535 100644
--- a/src/contour/Actions.cpp
+++ b/src/contour/Actions.cpp
@@ -88,6 +88,7 @@ optional fromString(string const& name)
mapAction("SwitchToPreviousTab"),
mapAction("SwitchToTabLeft"),
mapAction("SwitchToTabRight"),
+ mapAction("SetTabName"),
};
auto const lowerCaseName = toLower(name);
diff --git a/src/contour/Actions.h b/src/contour/Actions.h
index 14ccf1c397..be7e7b0d7c 100644
--- a/src/contour/Actions.h
+++ b/src/contour/Actions.h
@@ -88,6 +88,7 @@ struct SwitchToTab{ int position; };
struct SwitchToPreviousTab{};
struct SwitchToTabLeft{};
struct SwitchToTabRight{};
+struct SetTabName{};
// clang-format on
using Action = std::variant;
+ SwitchToTabRight,
+ SetTabName>;
std::optional fromString(std::string const& name);
@@ -276,6 +278,7 @@ namespace documentation
constexpr inline std::string_view SwitchToPreviousTab { "Switch to the previously focused tab" };
constexpr inline std::string_view SwitchToTabLeft { "Switch to tab to the left" };
constexpr inline std::string_view SwitchToTabRight { "Switch to tab to the right" };
+ constexpr inline std::string_view SetTabName { "Set the name of the current tab" };
} // namespace documentation
inline auto getDocumentation()
@@ -341,6 +344,7 @@ inline auto getDocumentation()
std::tuple { Action { SwitchToPreviousTab {} }, documentation::SwitchToPreviousTab },
std::tuple { Action { SwitchToTabLeft {} }, documentation::SwitchToTabLeft },
std::tuple { Action { SwitchToTabRight {} }, documentation::SwitchToTabRight },
+ std::tuple { Action { SetTabName {} }, documentation::SetTabName },
};
}
@@ -416,6 +420,7 @@ DECLARE_ACTION_FMT(MoveTabToRight)
DECLARE_ACTION_FMT(SwitchToPreviousTab)
DECLARE_ACTION_FMT(SwitchToTabLeft)
DECLARE_ACTION_FMT(SwitchToTabRight)
+DECLARE_ACTION_FMT(SetTabName)
// }}}
#undef DECLARE_ACTION_FMT
@@ -506,6 +511,7 @@ struct std::formatter: std::formatter
HANDLE_ACTION(SwitchToPreviousTab);
HANDLE_ACTION(SwitchToTabLeft);
HANDLE_ACTION(SwitchToTabRight);
+ HANDLE_ACTION(SetTabName);
if (std::holds_alternative(_action))
{
const auto action = std::get(_action);
diff --git a/src/contour/Config.h b/src/contour/Config.h
index 7447c5e867..78cc07f13f 100644
--- a/src/contour/Config.h
+++ b/src/contour/Config.h
@@ -180,11 +180,10 @@ struct MouseConfig
struct IndicatorConfig
{
std::string left { " {InputMode:Bold,Color=#FFFF00}"
- "{Tabs:ActiveColor=#FFFF00,Left= │ }"
"{SearchPrompt:Left= │ }"
"{TraceMode:Bold,Color=#FFFF00,Left= │ }"
"{ProtectedMode:Bold,Left= │ }" };
- std::string middle { "{Title:Left= « ,Right= » }" };
+ std::string middle { "{Tabs:ActiveColor=#FFFF00}" };
std::string right { "{HistoryLineCount:Faint,Color=#c0c0c0} │ {Clock:Bold}" };
};
diff --git a/src/contour/ConfigDocumentation.h b/src/contour/ConfigDocumentation.h
index c6e2a2a535..edd52aa2e6 100644
--- a/src/contour/ConfigDocumentation.h
+++ b/src/contour/ConfigDocumentation.h
@@ -813,6 +813,8 @@ constexpr StringLiteral InputMappingsConfig {
"p.\n"
"{comment} - WriteScreen Writes VT sequence in `chars` member to the screen (bypassing the "
"application).\n"
+ "{comment} - SetTabName Ask the user to assign a name to the active tab.\n"
+ "\n"
"input_mapping:\n"
};
diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp
index b491048608..2c5869abc9 100644
--- a/src/contour/TerminalSession.cpp
+++ b/src/contour/TerminalSession.cpp
@@ -149,6 +149,43 @@ namespace
settings.indicatorStatusLine.left = profile.statusLine.value().indicator.left;
settings.indicatorStatusLine.middle = profile.statusLine.value().indicator.middle;
settings.indicatorStatusLine.right = profile.statusLine.value().indicator.right;
+ settings.tabNamingMode = [&]() {
+ // try to find Tab section in one of the status line segments
+
+ std::string segment;
+ if (profile.statusLine.value().indicator.left.find("Tabs") != std::string::npos)
+ {
+ segment = profile.statusLine.value().indicator.left;
+ }
+ else if (profile.statusLine.value().indicator.middle.find("Tabs") != std::string::npos)
+ {
+ segment = profile.statusLine.value().indicator.middle;
+ }
+ else if (profile.statusLine.value().indicator.right.find("Tabs") != std::string::npos)
+ {
+ segment = profile.statusLine.value().indicator.right;
+ }
+
+ // check if indexing is defined
+ if (segment.find("Indexing=") != std::string::npos)
+ {
+ // cut the string after indexing=
+ std::string indexing = segment.substr(segment.find("Indexing=") + 9);
+ // cut right part of the string
+ indexing = indexing.substr(0, indexing.find(','));
+ indexing = indexing.substr(0, indexing.find('}'));
+
+ std::ranges::transform(
+ indexing, indexing.begin(), [](unsigned char c) { return std::tolower(c); });
+
+ if (indexing == "title")
+ {
+ return vtbackend::TabsNamingMode::Title;
+ }
+ }
+ return vtbackend::TabsNamingMode::Indexing;
+ }();
+
settings.syncWindowTitleWithHostWritableStatusDisplay =
profile.statusLine.value().syncWindowTitleWithHostWritableStatusDisplay;
if (auto const* p = preferredColorPalette(profile.colors.value(), colorPreference))
@@ -262,6 +299,7 @@ void TerminalSession::attachDisplay(display::TerminalDisplay& newDisplay)
void TerminalSession::scheduleRedraw()
{
_terminal.markScreenDirty();
+ _manager->update();
if (_display)
_display->scheduleRedraw();
}
@@ -1488,6 +1526,12 @@ bool TerminalSession::operator()(actions::SwitchToTabRight)
return true;
}
+bool TerminalSession::operator()(actions::SetTabName)
+{
+ terminal().requestTabName();
+ return true;
+}
+
// }}}
// {{{ implementation helpers
void TerminalSession::setDefaultCursor()
diff --git a/src/contour/TerminalSession.h b/src/contour/TerminalSession.h
index ddc808a5fd..9be4b04079 100644
--- a/src/contour/TerminalSession.h
+++ b/src/contour/TerminalSession.h
@@ -220,6 +220,14 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
~TerminalSession() override;
int id() const noexcept { return _id; }
+ std::optional name() const noexcept
+ {
+ if (terminal().tabName())
+ return terminal().tabName();
+ if (terminal().getTabsNamingMode() == vtbackend::TabsNamingMode::Title)
+ return terminal().windowTitle();
+ return std::nullopt;
+ }
/// Starts the VT background thread.
void start();
@@ -364,6 +372,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
bool operator()(actions::SwitchToPreviousTab);
bool operator()(actions::SwitchToTabLeft);
bool operator()(actions::SwitchToTabRight);
+ bool operator()(actions::SetTabName);
void scheduleRedraw();
diff --git a/src/contour/TerminalSessionManager.h b/src/contour/TerminalSessionManager.h
index cfc54b64c0..09163920bf 100644
--- a/src/contour/TerminalSessionManager.h
+++ b/src/contour/TerminalSessionManager.h
@@ -27,7 +27,6 @@ class TerminalSessionManager: public QAbstractListModel
TerminalSessionManager(ContourGuiApp& app);
contour::TerminalSession* createSessionInBackground();
- contour::TerminalSession* activateSession(TerminalSession* session, bool isNewSession = false);
Q_INVOKABLE contour::TerminalSession* createSession();
@@ -54,7 +53,10 @@ class TerminalSessionManager: public QAbstractListModel
display::TerminalDisplay* display = nullptr;
TerminalSession* getSession() { return _sessions[0]; }
+ void update() { updateStatusLine(); }
+
private:
+ contour::TerminalSession* activateSession(TerminalSession* session, bool isNewSession = false);
std::unique_ptr createPty(std::optional cwd);
[[nodiscard]] std::optional getSessionIndexOf(TerminalSession* session) const noexcept
@@ -73,9 +75,15 @@ class TerminalSessionManager: public QAbstractListModel
{
if (!_activeSession)
return;
-
_activeSession->terminal().setGuiTabInfoForStatusLine(vtbackend::TabsInfo {
- .tabCount = _sessions.size(),
+ .tabs = std::ranges::transform_view(_sessions,
+ [](auto* session) {
+ return vtbackend::TabsInfo::Tab {
+ .name = session->name(),
+ .color = vtbackend::RGBColor { 0, 0, 0 },
+ };
+ })
+ | ranges::to(),
.activeTabPosition = 1 + getSessionIndexOf(_activeSession).value_or(0),
});
}
diff --git a/src/vtbackend/Settings.h b/src/vtbackend/Settings.h
index 74be100ea5..1fa4dea995 100644
--- a/src/vtbackend/Settings.h
+++ b/src/vtbackend/Settings.h
@@ -24,6 +24,12 @@ struct RefreshInterval
explicit RefreshInterval(RefreshRate rate): value { static_cast(1000.0 / rate.value) } {}
};
+enum class TabsNamingMode : uint8_t
+{
+ Indexing,
+ Title
+};
+
/// Terminal settings, enabling hardware reset to be easier implemented.
struct Settings
{
@@ -91,6 +97,8 @@ struct Settings
bool fromSearchIntoInsertMode = true;
bool isInsertAfterYank = false;
+ TabsNamingMode tabNamingMode = TabsNamingMode::Indexing;
+
// TODO: we could configure also the number of lines of the host writable statusline and indicator
// statusline.
};
diff --git a/src/vtbackend/StatusLineBuilder.cpp b/src/vtbackend/StatusLineBuilder.cpp
index 2774375421..0044165894 100644
--- a/src/vtbackend/StatusLineBuilder.cpp
+++ b/src/vtbackend/StatusLineBuilder.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
using namespace std::string_view_literals;
@@ -156,6 +157,7 @@ std::optional makeStatusLineItem(
styles,
activeColor,
activeBackground,
+ std::nullopt, // separator
};
}
@@ -404,6 +406,9 @@ struct VTSerializer
return std::format("Search: {}█",
unicode::convert_to(std::u32string_view(vt.search().pattern)));
+ if (vt.inputHandler().isEditingPrompt())
+ return std::format("{}{}█", vt.prompt().prompt, vt.prompt().text);
+
return {};
}
@@ -443,10 +448,10 @@ struct VTSerializer
auto const tabsInfo = vt.guiTabsInfoForStatusLine();
std::string fragment;
- for (const auto position: std::views::iota(1u, tabsInfo.tabCount + 1))
+ for (const auto position: std::views::iota(1u, tabsInfo.tabs.size() + 1))
{
if (!fragment.empty())
- fragment += ' ';
+ fragment += tabs.separator.value_or("|");
auto const isActivePosition = position == tabsInfo.activeTabPosition;
auto const activePositionStylized =
@@ -459,7 +464,10 @@ struct VTSerializer
fragment += makeBackgroundColor(tabs.activeBackground);
}
- fragment += std::to_string(position);
+ if (tabsInfo.tabs[position - 1].name)
+ fragment += tabsInfo.tabs[position - 1].name.value();
+ else
+ fragment += std::to_string(position);
if (activePositionStylized)
fragment += SGRRESTORE();
diff --git a/src/vtbackend/StatusLineBuilder.h b/src/vtbackend/StatusLineBuilder.h
index fe64882920..5f6f76d266 100644
--- a/src/vtbackend/StatusLineBuilder.h
+++ b/src/vtbackend/StatusLineBuilder.h
@@ -43,6 +43,7 @@ namespace StatusLineDefinitions
{
std::optional activeColor;
std::optional activeBackground;
+ std::optional separator;
};
using Item = std::variant<
diff --git a/src/vtbackend/Terminal.cpp b/src/vtbackend/Terminal.cpp
index d9fd970c90..ef87e7ee8a 100644
--- a/src/vtbackend/Terminal.cpp
+++ b/src/vtbackend/Terminal.cpp
@@ -1567,6 +1567,16 @@ std::string const& Terminal::windowTitle() const noexcept
return _windowTitle;
}
+void Terminal::requestTabName()
+{
+ inputHandler().setTabName([&](std::string name) { _tabName = std::move(name); });
+}
+
+std::optional Terminal::tabName() const noexcept
+{
+ return _tabName;
+}
+
void Terminal::saveWindowTitle()
{
_savedWindowTitles.push(_windowTitle);
@@ -2195,6 +2205,16 @@ bool Terminal::setNewSearchTerm(std::u32string text, bool initiatedByDoubleClick
return true;
}
+void Terminal::setPrompt(std::string prompt)
+{
+ _prompt.prompt = std::move(prompt);
+}
+
+void Terminal::setPromptText(std::string text)
+{
+ _prompt.text = std::move(text);
+}
+
optional Terminal::searchReverse(u32string text, CellLocation searchPosition)
{
if (!setNewSearchTerm(std::move(text), false))
diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h
index c2676b7b56..9fe5bf2df3 100644
--- a/src/vtbackend/Terminal.h
+++ b/src/vtbackend/Terminal.h
@@ -42,6 +42,7 @@
#include
#include
#include
+#include
namespace vtbackend
{
@@ -143,6 +144,12 @@ struct Search
bool initiatedByDoubleClick = false;
};
+struct Prompt
+{
+ std::string prompt;
+ std::string text;
+};
+
// Mandates what execution mode the terminal will take to process VT sequences.
//
enum class ExecutionMode : uint8_t
@@ -209,7 +216,13 @@ class TraceHandler: public SequenceHandler
struct TabsInfo
{
- size_t tabCount = 1;
+ struct Tab
+ {
+ std::optional name;
+ Color color;
+ };
+
+ std::vector tabs;
size_t activeTabPosition = 1;
};
@@ -246,6 +259,7 @@ class Terminal
virtual void requestWindowResize(Width, Height) {}
virtual void requestShowHostWritableStatusLine() {}
virtual void setWindowTitle(std::string_view /*title*/) {}
+ virtual void setTabName(std::string_view /*title*/) {}
virtual void setTerminalProfile(std::string const& /*configProfileName*/) {}
virtual void discardImage(Image const&) {}
virtual void inputModeChanged(ViMode /*mode*/) {}
@@ -276,6 +290,7 @@ class Terminal
void requestWindowResize(Width, Height) override {}
void requestShowHostWritableStatusLine() override {}
void setWindowTitle(std::string_view /*title*/) override {}
+ void setTabName(std::string_view /*title*/) override {}
void setTerminalProfile(std::string const& /*configProfileName*/) override {}
void discardImage(Image const&) override {}
void inputModeChanged(ViMode /*mode*/) override {}
@@ -357,7 +372,7 @@ class Terminal
[[nodiscard]] CellLocation clampToScreen(CellLocation coord) const noexcept
{
- return { clampedLine(coord.line), clampedColumn(coord.column) };
+ return { .line = clampedLine(coord.line), .column = clampedColumn(coord.column) };
}
// Tests if given coordinate is within the visible screen area.
@@ -797,10 +812,14 @@ class Terminal
void setMouseTransport(MouseTransport transport);
void setMouseWheelMode(InputGenerator::MouseWheelMode mode);
void setWindowTitle(std::string_view title);
+ void setTabName(std::string_view title);
[[nodiscard]] std::string const& windowTitle() const noexcept;
+ [[nodiscard]] std::optional tabName() const noexcept;
[[nodiscard]] bool focused() const noexcept { return _focused; }
[[nodiscard]] Search& search() noexcept { return _search; }
[[nodiscard]] Search const& search() const noexcept { return _search; }
+ [[nodiscard]] Prompt& prompt() noexcept { return _prompt; }
+ [[nodiscard]] Prompt const& prompt() const noexcept { return _prompt; }
void saveWindowTitle();
void restoreWindowTitle();
void setTerminalProfile(std::string const& configProfileName);
@@ -882,6 +901,9 @@ class Terminal
bool setNewSearchTerm(std::u32string text, bool initiatedByDoubleClick);
void clearSearch();
+ void setPrompt(std::string prompt);
+ void setPromptText(std::string text);
+
// Tests if the grid cell at the given location does contain a word delimiter.
[[nodiscard]] bool wordDelimited(CellLocation position) const noexcept;
[[nodiscard]] bool wordDelimited(CellLocation position,
@@ -966,7 +988,10 @@ class Terminal
void resetStatusLineDefinition();
TabsInfo guiTabsInfoForStatusLine() const noexcept { return _guiTabInfoForStatusLine; }
- void setGuiTabInfoForStatusLine(TabsInfo info) { _guiTabInfoForStatusLine = info; }
+ void setGuiTabInfoForStatusLine(TabsInfo&& info) { _guiTabInfoForStatusLine = std::move(info); }
+
+ TabsNamingMode getTabsNamingMode() const noexcept { return _settings.tabNamingMode; }
+ void requestTabName();
private:
void mainLoop();
@@ -1080,7 +1105,9 @@ class Terminal
Viewport _viewport;
StatusLineDefinition _indicatorStatusLineDefinition;
+ // {{{ tabs info
TabsInfo _guiTabInfoForStatusLine;
+ // }}}
// {{{ selection states
std::unique_ptr _selection;
@@ -1147,6 +1174,7 @@ class Terminal
ActiveStatusDisplay _activeStatusDisplay = ActiveStatusDisplay::Main;
Search _search;
+ Prompt _prompt;
CursorDisplay _cursorDisplay = CursorDisplay::Steady;
CursorShape _cursorShape = CursorShape::Block;
@@ -1165,6 +1193,8 @@ class Terminal
std::string _windowTitle {};
std::stack _savedWindowTitles {};
+ std::optional _tabName {};
+
struct ModeDependantSequenceHandler
{
Terminal& terminal;
diff --git a/src/vtbackend/ViCommands.cpp b/src/vtbackend/ViCommands.cpp
index 61639d25df..0540980c1e 100644
--- a/src/vtbackend/ViCommands.cpp
+++ b/src/vtbackend/ViCommands.cpp
@@ -192,6 +192,28 @@ void ViCommands::searchCancel()
_terminal->screenUpdated();
}
+void ViCommands::promptStart(std::string const& query)
+{
+ _terminal->setPrompt(query);
+ _terminal->screenUpdated();
+}
+
+void ViCommands::promptDone()
+{
+ _terminal->screenUpdated();
+}
+
+void ViCommands::promptCancel()
+{
+ _terminal->screenUpdated();
+}
+
+void ViCommands::updatePromptText(std::string const& text)
+{
+ _terminal->setPromptText(text);
+ _terminal->screenUpdated();
+}
+
bool ViCommands::jumpToNextMatch(unsigned count)
{
for (unsigned i = 0; i < count; ++i)
diff --git a/src/vtbackend/ViCommands.h b/src/vtbackend/ViCommands.h
index 92ff55d31c..1398a107ba 100644
--- a/src/vtbackend/ViCommands.h
+++ b/src/vtbackend/ViCommands.h
@@ -46,6 +46,12 @@ class ViCommands: public ViInputHandler::Executor
void searchDone() override;
void searchCancel() override;
void updateSearchTerm(std::u32string const& text) override;
+
+ void promptStart(std::string const& query) override;
+ void promptDone() override;
+ void promptCancel() override;
+ void updatePromptText(std::string const& text) override;
+
bool jumpToNextMatch(unsigned count);
bool jumpToPreviousMatch(unsigned count);
diff --git a/src/vtbackend/ViInputHandler.cpp b/src/vtbackend/ViInputHandler.cpp
index d7300d70f4..4194081922 100644
--- a/src/vtbackend/ViInputHandler.cpp
+++ b/src/vtbackend/ViInputHandler.cpp
@@ -10,6 +10,8 @@
#include
+#include "vtbackend/InputGenerator.h"
+
using std::pair;
using std::vector;
using namespace std::string_view_literals;
@@ -337,7 +339,20 @@ Handled ViInputHandler::sendKeyPressEvent(Key key, Modifiers modifiers, Keyboard
if (eventType == KeyboardEventType::Release)
return Handled { true };
- if (_searchEditMode != SearchEditMode::Disabled)
+ if (_promptEditMode != PromptMode::Disabled)
+ {
+ // TODO: support cursor movements.
+ switch (key)
+ {
+ case Key::Backspace: return handlePromptEditor('\x08', modifiers);
+ case Key::Enter: return handlePromptEditor('\x0D', modifiers);
+ case Key::Escape: return handlePromptEditor('\x1B', modifiers);
+ default: break;
+ }
+ return Handled { true };
+ }
+
+ if (_searchEditMode != PromptMode::Disabled)
{
// TODO: support cursor movements.
switch (key)
@@ -432,61 +447,110 @@ void ViInputHandler::startSearchExternally()
_executor->searchStart();
if (_viMode != ViMode::Insert)
- _searchEditMode = SearchEditMode::Enabled;
+ _searchEditMode = PromptMode::Enabled;
else
{
- _searchEditMode = SearchEditMode::ExternallyEnabled;
+ _searchEditMode = PromptMode::ExternallyEnabled;
setMode(ViMode::Normal);
// ^^^ So that we can see the statusline (which contains the search edit field),
// AND it's weird to be in insert mode while typing in the search term anyways.
}
}
-Handled ViInputHandler::handleSearchEditor(char32_t ch, Modifiers modifiers)
+auto handleEditor(char32_t ch,
+ Modifiers modifiers,
+ auto& where,
+ PromptMode& promptEditMode,
+ auto& settings,
+ auto setViMode,
+ auto cancel,
+ auto done,
+ auto update)
{
- assert(_searchEditMode != SearchEditMode::Disabled);
-
switch (InputMatch { .modifiers = modifiers, .ch = ch })
{
- case '\x1B'_key:
- _searchTerm.clear();
- if (_searchEditMode == SearchEditMode::ExternallyEnabled)
- setMode(ViMode::Insert);
- _searchEditMode = SearchEditMode::Disabled;
- _executor->searchCancel();
- _executor->updateSearchTerm(_searchTerm);
- break;
- case '\x0D'_key:
- if (_settings.fromSearchIntoInsertMode && _searchEditMode == SearchEditMode::ExternallyEnabled)
- setMode(ViMode::Insert);
- _searchEditMode = SearchEditMode::Disabled;
- _executor->searchDone();
- _executor->updateSearchTerm(_searchTerm);
- break;
+ case '\x1B'_key: {
+ where.clear();
+ if (promptEditMode == PromptMode::ExternallyEnabled)
+ setViMode(ViMode::Insert);
+ promptEditMode = PromptMode::Enabled;
+ cancel();
+ update(where);
+ }
+ break;
+ case '\x0D'_key: {
+ if (settings.fromSearchIntoInsertMode && promptEditMode == PromptMode::ExternallyEnabled)
+ setViMode(ViMode::Insert);
+ promptEditMode = PromptMode::Disabled;
+ done();
+ }
+ break;
case '\x08'_key:
case '\x7F'_key:
- if (!_searchTerm.empty())
- _searchTerm.resize(_searchTerm.size() - 1);
- _executor->updateSearchTerm(_searchTerm);
+ if (!where.empty())
+ where.resize(where.size() - 1);
+ update(where);
break;
case Modifier::Control | 'L':
case Modifier::Control | 'U':
- _searchTerm.clear();
- _executor->updateSearchTerm(_searchTerm);
+ where.clear();
+ update(where);
break;
case Modifier::Control | 'A': // TODO: move cursor to BOL
case Modifier::Control | 'E': // TODO: move cursor to EOL
default:
if (ch >= 0x20 && modifiers.without(Modifier::Shift).none())
{
- _searchTerm += ch;
- _executor->updateSearchTerm(_searchTerm);
+ where += ch;
+ update(where);
}
else
errorLog()("ViInputHandler: Receiving control code {}+0x{:02X} in search mode. Ignoring.",
modifiers,
(unsigned) ch);
}
+}
+
+Handled ViInputHandler::handleSearchEditor(char32_t ch, Modifiers modifiers)
+{
+ assert(_searchEditMode != PromptMode::Disabled);
+
+ handleEditor(
+ ch,
+ modifiers,
+ _searchTerm,
+ _searchEditMode,
+ _settings,
+ [&](auto mode) { setMode(mode); },
+ [&]() { _executor->searchCancel(); },
+ [&]() { _executor->searchDone(); },
+ [&](const auto& val) { _executor->updateSearchTerm(val); });
+
+ return Handled { true };
+}
+
+Handled ViInputHandler::handlePromptEditor(char32_t ch, Modifiers modifiers)
+{
+ assert(_promptEditMode != PromptMode::Disabled);
+
+ handleEditor(
+ ch,
+ modifiers,
+ _promptText,
+ _promptEditMode,
+ _settings,
+ [&](auto mode) { setMode(mode); },
+ [&]() { _executor->promptCancel(); },
+ [&]() {
+ _executor->promptDone();
+ if (_setTabNameCallback)
+ {
+ _setTabNameCallback.value()(_promptText);
+ setMode(ViMode::Insert);
+ _setTabNameCallback = std::nullopt;
+ }
+ },
+ [&](const auto& val) { _executor->updatePromptText(val); });
return Handled { true };
}
@@ -496,9 +560,12 @@ Handled ViInputHandler::sendCharPressEvent(char32_t ch, Modifiers modifiers, Key
if (eventType == KeyboardEventType::Release)
return Handled { true };
- if (_searchEditMode != SearchEditMode::Disabled)
+ if (_searchEditMode != PromptMode::Disabled)
return handleSearchEditor(ch, modifiers);
+ if (_promptEditMode != PromptMode::Disabled)
+ return handlePromptEditor(ch, modifiers);
+
if (_viMode == ViMode::Insert)
return Handled { false };
@@ -568,7 +635,7 @@ bool ViInputHandler::parseCount(char32_t ch, Modifiers modifiers)
void ViInputHandler::startSearch()
{
- _searchEditMode = SearchEditMode::Enabled;
+ _searchEditMode = PromptMode::Enabled;
_executor->searchStart();
}
diff --git a/src/vtbackend/ViInputHandler.h b/src/vtbackend/ViInputHandler.h
index 7abb3f23f0..e93bd49927 100644
--- a/src/vtbackend/ViInputHandler.h
+++ b/src/vtbackend/ViInputHandler.h
@@ -144,7 +144,7 @@ struct RectangularHighlight { CellLocation from; CellLocation to; };
using HighlightRange = std::variant;
-enum class SearchEditMode : uint8_t
+enum class PromptMode : uint8_t
{
Disabled,
Enabled,
@@ -177,6 +177,11 @@ class ViInputHandler: public InputHandler
virtual void searchCancel() = 0;
virtual void updateSearchTerm(std::u32string const& text) = 0;
+ virtual void promptStart(std::string const& query) = 0;
+ virtual void promptDone() = 0;
+ virtual void promptCancel() = 0;
+ virtual void updatePromptText(std::string const& text) = 0;
+
virtual void scrollViewport(ScrollOffset delta) = 0;
// Starts searching for the word under the cursor position in reverse order.
@@ -212,13 +217,29 @@ class ViInputHandler: public InputHandler
crispy::unreachable();
}
- [[nodiscard]] bool isEditingSearch() const noexcept
- {
- return _searchEditMode != SearchEditMode::Disabled;
- }
+ [[nodiscard]] bool isEditingSearch() const noexcept { return _searchEditMode != PromptMode::Disabled; }
+ [[nodiscard]] bool isEditingPrompt() const noexcept { return _promptEditMode != PromptMode::Disabled; }
void startSearchExternally();
+ void setTabName(auto&& callback)
+ {
+ _promptText.clear();
+ _executor->promptStart("Tab name: ");
+
+ if (_viMode != ViMode::Insert)
+ _promptEditMode = PromptMode::Enabled;
+ else
+ {
+ _promptEditMode = PromptMode::ExternallyEnabled;
+ setMode(ViMode::Normal);
+ // ^^^ So that we can see the statusline (which contains the search edit field),
+ // AND it's weird to be in insert mode while typing in the prompt term anyways.
+ }
+
+ _setTabNameCallback = callback;
+ }
+
void setSearchModeSwitch(bool enabled);
void clearSearch();
@@ -250,14 +271,17 @@ class ViInputHandler: public InputHandler
bool parseCount(char32_t ch, Modifiers modifiers);
bool parseTextObject(char32_t ch, Modifiers modifiers);
Handled handleSearchEditor(char32_t ch, Modifiers modifiers);
+ Handled handlePromptEditor(char32_t ch, Modifiers modifiers);
Handled handleModeSwitches(char32_t ch, Modifiers modifiers);
void startSearch();
ViMode _viMode = ViMode::Normal;
- SearchEditMode _searchEditMode = SearchEditMode::Disabled;
+ PromptMode _searchEditMode = PromptMode::Disabled;
+ PromptMode _promptEditMode = PromptMode::Disabled;
bool _searchExternallyActivated = false;
std::u32string _searchTerm;
+ std::string _promptText;
std::string _pendingInput;
CommandHandlerMap _normalMode;
@@ -265,6 +289,7 @@ class ViInputHandler: public InputHandler
unsigned _count = 0;
char32_t _lastChar = 0;
gsl::not_null _executor;
+ std::optional> _setTabNameCallback { std::nullopt };
};
} // namespace vtbackend