mirror of
https://github.com/m1ngsama/TUT.git
synced 2025-12-25 02:57:08 +00:00
feat: Add Vimium-style link hints and vim keybindings infrastructure
- Add LINK_HINTS mode for Vimium-style link navigation - Implement 'f' key to activate link hints mode - Add visual mode support (v/V keys) - Implement marks support (m[a-z] to set, '[a-z] to jump) - Add tab navigation keys (gt/gT for next/previous tab) - Add new actions: * SHOW_LINK_HINTS - activate link hints overlay * FOLLOW_LINK_HINT - follow link by hint letters * ENTER_VISUAL_MODE / ENTER_VISUAL_LINE_MODE * SET_MARK / GOTO_MARK - vim-style position bookmarks * NEXT_TAB / PREV_TAB - tab navigation * YANK - copy selected text This brings modern browser vim plugin functionality (like Vimium) to the terminal, making link navigation much faster than traditional tab-through methods.
This commit is contained in:
parent
638a08e437
commit
ac988dfda8
2 changed files with 160 additions and 13 deletions
|
|
@ -23,7 +23,48 @@ public:
|
||||||
result.has_count = false;
|
result.has_count = false;
|
||||||
result.count = 1;
|
result.count = 1;
|
||||||
|
|
||||||
// Handle digit input for count or 'f' command
|
// Handle multi-char commands like 'gg', 'gt', 'gT', 'm', '
|
||||||
|
if (!buffer.empty()) {
|
||||||
|
if (buffer == "g") {
|
||||||
|
if (ch == 't') {
|
||||||
|
result.action = Action::NEXT_TAB;
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
} else if (ch == 'T') {
|
||||||
|
result.action = Action::PREV_TAB;
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else if (buffer == "m") {
|
||||||
|
// Set mark with letter
|
||||||
|
if (std::isalpha(ch)) {
|
||||||
|
result.action = Action::SET_MARK;
|
||||||
|
result.text = std::string(1, static_cast<char>(ch));
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
} else if (buffer == "'") {
|
||||||
|
// Jump to mark
|
||||||
|
if (std::isalpha(ch)) {
|
||||||
|
result.action = Action::GOTO_MARK;
|
||||||
|
result.text = std::string(1, static_cast<char>(ch));
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
count_buffer.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle digit input for count
|
||||||
if (std::isdigit(ch) && (ch != '0' || !count_buffer.empty())) {
|
if (std::isdigit(ch) && (ch != '0' || !count_buffer.empty())) {
|
||||||
count_buffer += static_cast<char>(ch);
|
count_buffer += static_cast<char>(ch);
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -116,16 +157,33 @@ public:
|
||||||
count_buffer.clear();
|
count_buffer.clear();
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
// 'f' command: follow link by number
|
// 'f' command: vimium-style link hints
|
||||||
if (result.has_count) {
|
result.action = Action::SHOW_LINK_HINTS;
|
||||||
result.action = Action::FOLLOW_LINK_NUM;
|
mode = InputMode::LINK_HINTS;
|
||||||
result.number = result.count;
|
buffer.clear();
|
||||||
count_buffer.clear();
|
count_buffer.clear();
|
||||||
} else {
|
break;
|
||||||
// Enter link follow mode, wait for number
|
case 'v':
|
||||||
mode = InputMode::LINK;
|
// Enter visual mode
|
||||||
buffer = "f";
|
result.action = Action::ENTER_VISUAL_MODE;
|
||||||
}
|
mode = InputMode::VISUAL;
|
||||||
|
count_buffer.clear();
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
// Enter visual line mode
|
||||||
|
result.action = Action::ENTER_VISUAL_LINE_MODE;
|
||||||
|
mode = InputMode::VISUAL_LINE;
|
||||||
|
count_buffer.clear();
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
// Set mark (wait for next char)
|
||||||
|
buffer = "m";
|
||||||
|
count_buffer.clear();
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
// Jump to mark (wait for next char)
|
||||||
|
buffer = "'";
|
||||||
|
count_buffer.clear();
|
||||||
break;
|
break;
|
||||||
case ':':
|
case ':':
|
||||||
mode = InputMode::COMMAND;
|
mode = InputMode::COMMAND;
|
||||||
|
|
@ -257,6 +315,75 @@ public:
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InputResult process_link_hints_mode(int ch) {
|
||||||
|
InputResult result;
|
||||||
|
result.action = Action::NONE;
|
||||||
|
|
||||||
|
if (ch == 27) {
|
||||||
|
// ESC cancels link hints mode
|
||||||
|
mode = InputMode::NORMAL;
|
||||||
|
buffer.clear();
|
||||||
|
return result;
|
||||||
|
} else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) {
|
||||||
|
// Backspace removes last character
|
||||||
|
if (!buffer.empty()) {
|
||||||
|
buffer.pop_back();
|
||||||
|
} else {
|
||||||
|
mode = InputMode::NORMAL;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else if (std::isalpha(ch)) {
|
||||||
|
// Add character to buffer
|
||||||
|
buffer += std::tolower(static_cast<char>(ch));
|
||||||
|
|
||||||
|
// Try to match link hint
|
||||||
|
result.action = Action::FOLLOW_LINK_HINT;
|
||||||
|
result.text = buffer;
|
||||||
|
|
||||||
|
// Mode will be reset by browser if link is followed
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputResult process_visual_mode(int ch) {
|
||||||
|
InputResult result;
|
||||||
|
result.action = Action::NONE;
|
||||||
|
|
||||||
|
if (ch == 27 || ch == 'v') {
|
||||||
|
// ESC or 'v' exits visual mode
|
||||||
|
mode = InputMode::NORMAL;
|
||||||
|
return result;
|
||||||
|
} else if (ch == 'y') {
|
||||||
|
// Yank (copy) selected text
|
||||||
|
result.action = Action::YANK;
|
||||||
|
mode = InputMode::NORMAL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through navigation commands
|
||||||
|
switch (ch) {
|
||||||
|
case 'j':
|
||||||
|
case KEY_DOWN:
|
||||||
|
result.action = Action::SCROLL_DOWN;
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
case KEY_UP:
|
||||||
|
result.action = Action::SCROLL_UP;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
case KEY_LEFT:
|
||||||
|
// In visual mode, h/l could extend selection
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
case KEY_RIGHT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
InputHandler::InputHandler() : pImpl(std::make_unique<Impl>()) {}
|
InputHandler::InputHandler() : pImpl(std::make_unique<Impl>()) {}
|
||||||
|
|
@ -273,6 +400,11 @@ InputResult InputHandler::handle_key(int ch) {
|
||||||
return pImpl->process_search_mode(ch);
|
return pImpl->process_search_mode(ch);
|
||||||
case InputMode::LINK:
|
case InputMode::LINK:
|
||||||
return pImpl->process_link_mode(ch);
|
return pImpl->process_link_mode(ch);
|
||||||
|
case InputMode::LINK_HINTS:
|
||||||
|
return pImpl->process_link_hints_mode(ch);
|
||||||
|
case InputMode::VISUAL:
|
||||||
|
case InputMode::VISUAL_LINE:
|
||||||
|
return pImpl->process_visual_mode(ch);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ enum class InputMode {
|
||||||
NORMAL,
|
NORMAL,
|
||||||
COMMAND,
|
COMMAND,
|
||||||
SEARCH,
|
SEARCH,
|
||||||
LINK
|
LINK,
|
||||||
|
LINK_HINTS, // Vimium-style 'f' mode
|
||||||
|
VISUAL, // Visual mode
|
||||||
|
VISUAL_LINE // Visual line mode
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Action {
|
enum class Action {
|
||||||
|
|
@ -28,12 +31,24 @@ enum class Action {
|
||||||
FOLLOW_LINK,
|
FOLLOW_LINK,
|
||||||
GOTO_LINK, // Jump to specific link by number
|
GOTO_LINK, // Jump to specific link by number
|
||||||
FOLLOW_LINK_NUM, // Follow specific link by number (f command)
|
FOLLOW_LINK_NUM, // Follow specific link by number (f command)
|
||||||
|
SHOW_LINK_HINTS, // Activate link hints mode ('f')
|
||||||
|
FOLLOW_LINK_HINT, // Follow link by hint letters
|
||||||
GO_BACK,
|
GO_BACK,
|
||||||
GO_FORWARD,
|
GO_FORWARD,
|
||||||
OPEN_URL,
|
OPEN_URL,
|
||||||
REFRESH,
|
REFRESH,
|
||||||
QUIT,
|
QUIT,
|
||||||
HELP
|
HELP,
|
||||||
|
ENTER_VISUAL_MODE, // Start visual mode
|
||||||
|
ENTER_VISUAL_LINE_MODE, // Start visual line mode
|
||||||
|
SET_MARK, // Set a mark (m + letter)
|
||||||
|
GOTO_MARK, // Jump to mark (' + letter)
|
||||||
|
YANK, // Copy selected text
|
||||||
|
NEXT_TAB, // gt - next tab
|
||||||
|
PREV_TAB, // gT - previous tab
|
||||||
|
NEW_TAB, // :tabnew
|
||||||
|
CLOSE_TAB, // :tabc
|
||||||
|
TOGGLE_MOUSE // Toggle mouse support
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InputResult {
|
struct InputResult {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue