|
|
@@ -940,6 +940,155 @@ void EditorCore::record_key_sequence(const std::string& key_sequence) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// === Rectangles ===
|
|
|
+
|
|
|
+void EditorCore::kill_rectangle() {
|
|
|
+ auto& buf = buffer();
|
|
|
+ Position cursor = active_window_->cursor();
|
|
|
+
|
|
|
+ auto region = buf.get_region(cursor);
|
|
|
+ if (!region) {
|
|
|
+ set_message("No active region");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure start is top-left, end is bottom-right
|
|
|
+ Position start = region->start;
|
|
|
+ Position end = region->end;
|
|
|
+
|
|
|
+ if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
|
|
|
+ std::swap(start, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ rectangle_kill_ring_.clear();
|
|
|
+
|
|
|
+ // Extract rectangle line by line
|
|
|
+ for (size_t line = start.line; line <= end.line; ++line) {
|
|
|
+ if (line >= buf.line_count()) break;
|
|
|
+
|
|
|
+ std::string line_text = buf.line(line);
|
|
|
+ size_t start_col = (line == start.line) ? start.column : std::min(start.column, end.column);
|
|
|
+ size_t end_col = (line == end.line) ? end.column : std::max(start.column, end.column);
|
|
|
+
|
|
|
+ // Ensure we don't go beyond the line length
|
|
|
+ start_col = std::min(start_col, line_text.size());
|
|
|
+ end_col = std::min(end_col, line_text.size());
|
|
|
+
|
|
|
+ if (start_col < end_col) {
|
|
|
+ rectangle_kill_ring_.push_back(line_text.substr(start_col, end_col - start_col));
|
|
|
+ } else {
|
|
|
+ rectangle_kill_ring_.push_back("");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now delete the rectangle content (from bottom to top to preserve line indices)
|
|
|
+ for (int line = static_cast<int>(end.line); line >= static_cast<int>(start.line); --line) {
|
|
|
+ if (line >= static_cast<int>(buf.line_count())) continue;
|
|
|
+
|
|
|
+ std::string line_text = buf.line(line);
|
|
|
+ size_t start_col = (line == static_cast<int>(start.line)) ? start.column : std::min(start.column, end.column);
|
|
|
+ size_t end_col = (line == static_cast<int>(end.line)) ? end.column : std::max(start.column, end.column);
|
|
|
+
|
|
|
+ start_col = std::min(start_col, line_text.size());
|
|
|
+ end_col = std::min(end_col, line_text.size());
|
|
|
+
|
|
|
+ if (start_col < end_col) {
|
|
|
+ Range del_range{Position(line, start_col), Position(line, end_col)};
|
|
|
+ buf.erase(del_range);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ buf.deactivate_mark();
|
|
|
+ emit_event(EditorEvent::BufferModified);
|
|
|
+
|
|
|
+ set_message(std::string("Rectangle killed (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
|
|
|
+}
|
|
|
+
|
|
|
+void EditorCore::yank_rectangle() {
|
|
|
+ if (rectangle_kill_ring_.empty()) {
|
|
|
+ set_message("No rectangle in kill ring");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto& buf = buffer();
|
|
|
+ Position cursor = active_window_->cursor();
|
|
|
+
|
|
|
+ // Insert rectangle starting at cursor position
|
|
|
+ for (size_t i = 0; i < rectangle_kill_ring_.size(); ++i) {
|
|
|
+ Position insert_pos{cursor.line + i, cursor.column};
|
|
|
+
|
|
|
+ // Ensure we have enough lines
|
|
|
+ while (buf.line_count() <= insert_pos.line) {
|
|
|
+ buf.insert_newline(Position{buf.line_count() - 1, buf.line(buf.line_count() - 1).size()});
|
|
|
+ }
|
|
|
+
|
|
|
+ // Pad line with spaces if necessary
|
|
|
+ std::string current_line = buf.line(insert_pos.line);
|
|
|
+ if (current_line.size() < insert_pos.column) {
|
|
|
+ std::string padding(insert_pos.column - current_line.size(), ' ');
|
|
|
+ buf.insert(Position{insert_pos.line, current_line.size()}, padding);
|
|
|
+ }
|
|
|
+
|
|
|
+ buf.insert(insert_pos, rectangle_kill_ring_[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ emit_event(EditorEvent::BufferModified);
|
|
|
+ set_message(std::string("Rectangle yanked (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
|
|
|
+}
|
|
|
+
|
|
|
+void EditorCore::string_rectangle(const std::string& text) {
|
|
|
+ auto& buf = buffer();
|
|
|
+ Position cursor = active_window_->cursor();
|
|
|
+
|
|
|
+ auto region = buf.get_region(cursor);
|
|
|
+ if (!region) {
|
|
|
+ set_message("No active region");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Position start = region->start;
|
|
|
+ Position end = region->end;
|
|
|
+
|
|
|
+ if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
|
|
|
+ std::swap(start, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fill rectangle with the given text
|
|
|
+ for (size_t line = start.line; line <= end.line; ++line) {
|
|
|
+ if (line >= buf.line_count()) break;
|
|
|
+
|
|
|
+ std::string line_text = buf.line(line);
|
|
|
+ size_t start_col = std::min(start.column, end.column);
|
|
|
+ size_t end_col = std::max(start.column, end.column);
|
|
|
+
|
|
|
+ // Pad line if necessary
|
|
|
+ if (line_text.size() < end_col) {
|
|
|
+ std::string padding(end_col - line_text.size(), ' ');
|
|
|
+ buf.insert(Position{line, line_text.size()}, padding);
|
|
|
+ line_text = buf.line(line); // Refresh
|
|
|
+ }
|
|
|
+
|
|
|
+ // Replace rectangle content with text
|
|
|
+ if (start_col < line_text.size()) {
|
|
|
+ end_col = std::min(end_col, line_text.size());
|
|
|
+ if (start_col < end_col) {
|
|
|
+ Range replace_range{Position(line, start_col), Position(line, end_col)};
|
|
|
+ std::string fill_text = text;
|
|
|
+ if (fill_text.size() > end_col - start_col) {
|
|
|
+ fill_text = fill_text.substr(0, end_col - start_col);
|
|
|
+ } else if (fill_text.size() < end_col - start_col) {
|
|
|
+ fill_text += std::string(end_col - start_col - fill_text.size(), ' ');
|
|
|
+ }
|
|
|
+ buf.replace(replace_range, fill_text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ buf.deactivate_mark();
|
|
|
+ emit_event(EditorEvent::BufferModified);
|
|
|
+ set_message("Rectangle filled");
|
|
|
+}
|
|
|
+
|
|
|
// === Private ===
|
|
|
|
|
|
void EditorCore::emit_event(EditorEvent event) {
|