diff --git a/src/browser.cpp b/src/browser.cpp index e763c6a..fb38dac 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -456,6 +456,7 @@ public: case InputMode::NORMAL: mode_str = "NORMAL"; break; case InputMode::COMMAND: case InputMode::SEARCH: mode_str = input_handler.get_buffer(); break; + case InputMode::FORM_EDIT: mode_str = "-- INSERT -- " + input_handler.get_buffer(); break; default: mode_str = ""; break; } framebuffer->set_text(1, y, mode_str, colors::STATUSBAR_FG, colors::STATUSBAR_BG); @@ -540,7 +541,25 @@ public: break; case Action::FOLLOW_LINK: - if (active_link >= 0 && active_link < static_cast(current_tree.links.size())) { + // If on a form field, activate it instead of following link + if (active_field >= 0 && active_field < static_cast(current_tree.form_fields.size())) { + auto* field = current_tree.form_fields[active_field]; + if (field) { + if (field->input_type == "text" || field->input_type == "password") { + // Enter edit mode + input_handler.set_mode(InputMode::FORM_EDIT); + input_handler.set_buffer(field->value); + status_message = "-- INSERT --"; + } else if (field->input_type == "checkbox") { + // Toggle checkbox + field->checked = !field->checked; + status_message = field->checked ? "☑ Checked" : "☐ Unchecked"; + } else if (field->input_type == "submit" || field->element_type == ElementType::BUTTON) { + // TODO: Submit form + status_message = "Form submit (not yet implemented)"; + } + } + } else if (active_link >= 0 && active_link < static_cast(current_tree.links.size())) { start_async_load(current_tree.links[active_link].url); } break; @@ -609,6 +628,66 @@ public: show_history(); break; + case Action::NEXT_FIELD: + if (!current_tree.form_fields.empty()) { + // Save current text if in edit mode + if (input_handler.get_mode() == InputMode::FORM_EDIT && + active_field >= 0 && active_field < static_cast(current_tree.form_fields.size())) { + auto* field = current_tree.form_fields[active_field]; + if (field && (field->input_type == "text" || field->input_type == "password")) { + field->value = result.text; + } + } + + // Move to next field + if (active_field < 0) { + active_field = 0; // First field + } else { + active_field = (active_field + 1) % current_tree.form_fields.size(); + } + + // Auto-scroll to field + // TODO: Implement scroll to field + status_message = "Field " + std::to_string(active_field + 1) + "/" + + std::to_string(current_tree.form_fields.size()); + } + break; + + case Action::PREV_FIELD: + if (!current_tree.form_fields.empty()) { + // Save current text if in edit mode + if (input_handler.get_mode() == InputMode::FORM_EDIT && + active_field >= 0 && active_field < static_cast(current_tree.form_fields.size())) { + auto* field = current_tree.form_fields[active_field]; + if (field && (field->input_type == "text" || field->input_type == "password")) { + field->value = result.text; + } + } + + // Move to previous field + if (active_field < 0) { + active_field = current_tree.form_fields.size() - 1; // Last field + } else { + active_field = (active_field - 1 + current_tree.form_fields.size()) % + current_tree.form_fields.size(); + } + + status_message = "Field " + std::to_string(active_field + 1) + "/" + + std::to_string(current_tree.form_fields.size()); + } + break; + + case Action::EDIT_TEXT: + // Update field value in real-time + if (active_field >= 0 && active_field < static_cast(current_tree.form_fields.size())) { + auto* field = current_tree.form_fields[active_field]; + if (field && (field->input_type == "text" || field->input_type == "password")) { + field->value = result.text; + status_message = "Editing: " + result.text; + } + } + break; + case Action::QUIT: break; // 在main loop处理 diff --git a/src/input_handler.cpp b/src/input_handler.cpp index 166ad9f..7e3f7d9 100644 --- a/src/input_handler.cpp +++ b/src/input_handler.cpp @@ -125,6 +125,7 @@ public: count_buffer.clear(); break; case '\t': + // Tab can navigate both links and fields - browser will decide result.action = Action::NEXT_LINK; count_buffer.clear(); break; @@ -135,7 +136,7 @@ public: break; case '\n': case '\r': - // If count buffer has a number, jump to that link + // Enter can follow links or activate fields - browser will decide if (result.has_count) { result.action = Action::GOTO_LINK; result.number = result.count; @@ -144,6 +145,11 @@ public: } count_buffer.clear(); break; + case 'i': + // 'i' to focus on first form field (like vim insert mode) + result.action = Action::NEXT_FIELD; + count_buffer.clear(); + break; case 'f': // 'f' command: vimium-style link hints result.action = Action::SHOW_LINK_HINTS; @@ -334,6 +340,52 @@ public: return result; } + InputResult process_form_edit_mode(int ch) { + InputResult result; + result.action = Action::NONE; + + if (ch == 27) { + // ESC exits form edit mode + mode = InputMode::NORMAL; + buffer.clear(); + return result; + } else if (ch == '\n' || ch == '\r') { + // Enter submits the text + result.action = Action::EDIT_TEXT; + result.text = buffer; + 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(); + } + return result; + } else if (ch == '\t') { + // Tab moves to next field while saving current text + result.action = Action::NEXT_FIELD; + result.text = buffer; + buffer.clear(); + return result; + } else if (ch == KEY_BTAB) { + // Shift+Tab moves to previous field while saving current text + result.action = Action::PREV_FIELD; + result.text = buffer; + buffer.clear(); + return result; + } else if (std::isprint(ch)) { + // Add printable characters to buffer + buffer += static_cast(ch); + // Return EDIT_TEXT to update in real-time + result.action = Action::EDIT_TEXT; + result.text = buffer; + return result; + } + + return result; + } + }; InputHandler::InputHandler() : pImpl(std::make_unique()) {} @@ -352,6 +404,8 @@ InputResult InputHandler::handle_key(int ch) { return pImpl->process_link_mode(ch); case InputMode::LINK_HINTS: return pImpl->process_link_hints_mode(ch); + case InputMode::FORM_EDIT: + return pImpl->process_form_edit_mode(ch); default: break; } @@ -375,6 +429,14 @@ void InputHandler::reset() { pImpl->count_buffer.clear(); } +void InputHandler::set_mode(InputMode mode) { + pImpl->mode = mode; +} + +void InputHandler::set_buffer(const std::string& buffer) { + pImpl->buffer = buffer; +} + void InputHandler::set_status_callback(std::function callback) { pImpl->status_callback = callback; } diff --git a/src/input_handler.h b/src/input_handler.h index 2df0a0d..88eacc7 100644 --- a/src/input_handler.h +++ b/src/input_handler.h @@ -9,7 +9,8 @@ enum class InputMode { COMMAND, SEARCH, LINK, - LINK_HINTS // Vimium-style 'f' mode + LINK_HINTS, // Vimium-style 'f' mode + FORM_EDIT // Form field editing mode }; enum class Action { @@ -42,7 +43,13 @@ enum class Action { ADD_BOOKMARK, // Add current page to bookmarks (B) REMOVE_BOOKMARK, // Remove current page from bookmarks (D) SHOW_BOOKMARKS, // Show bookmarks page (:bookmarks) - SHOW_HISTORY // Show history page (:history) + SHOW_HISTORY, // Show history page (:history) + NEXT_FIELD, // Move to next form field (Tab) + PREV_FIELD, // Move to previous form field (Shift+Tab) + ACTIVATE_FIELD, // Activate current field for editing (Enter) + TOGGLE_CHECKBOX, // Toggle checkbox state + EDIT_TEXT, // Edit text input (updates text buffer) + SUBMIT_FORM // Submit form (Enter on submit button) }; struct InputResult { @@ -62,6 +69,8 @@ public: InputMode get_mode() const; std::string get_buffer() const; void reset(); + void set_mode(InputMode mode); + void set_buffer(const std::string& buffer); void set_status_callback(std::function callback); private: diff --git a/test_form.html b/test_form.html new file mode 100644 index 0000000..4379ba8 --- /dev/null +++ b/test_form.html @@ -0,0 +1,24 @@ + + + + Form Test + + +

Test Form

+ +
+

Name:

+

Email:

+

Password:

+ +

Subscribe to newsletter

+

I agree to terms

+ +

+

+
+ +

Links

+

Example Link

+ +