#include "CopyMode.h" #include CopyCursor applyCopyMotion(CopyCursor cur, CopyMotion motion, int viewportRows, int minRow, int maxRow, int lineLength) { if (maxRow < minRow) { return cur; } // Column clamp: stay inside the row's painted cells (0 when the row is // empty). cursorCol may sit at lineLength-1 at most. const auto clampCol = [lineLength](int col) { return std::clamp(col, 0, std::max(0, lineLength - 1)); }; cur.row = std::clamp(cur.row, minRow, maxRow); switch (motion) { case CopyMotion::Left: // Wrapping left: off the row head steps up to the previous row's end // (vim's h at line start), clamped at the buffer top. if (cur.col > 0) { --cur.col; } else if (cur.row > minRow) { --cur.row; cur.col = std::max(0, lineLength - 1); } break; case CopyMotion::Right: if (cur.col < lineLength - 1) { ++cur.col; } else if (cur.row < maxRow) { ++cur.row; cur.col = 0; } break; case CopyMotion::Up: cur.row = std::max(minRow, cur.row - 1); cur.col = clampCol(cur.col); break; case CopyMotion::Down: cur.row = std::min(maxRow, cur.row + 1); cur.col = clampCol(cur.col); break; case CopyMotion::PageUp: cur.row = std::max(minRow, cur.row - std::max(1, viewportRows)); cur.col = clampCol(cur.col); break; case CopyMotion::PageDown: cur.row = std::min(maxRow, cur.row + std::max(1, viewportRows)); cur.col = clampCol(cur.col); break; case CopyMotion::LineHome: cur.col = 0; break; case CopyMotion::LineEnd: cur.col = std::max(0, lineLength - 1); break; case CopyMotion::BufferTop: cur.row = minRow; cur.col = clampCol(cur.col); break; case CopyMotion::BufferBottom: cur.row = maxRow; cur.col = clampCol(cur.col); break; } cur.row = std::clamp(cur.row, minRow, maxRow); cur.col = clampCol(cur.col); return cur; } CopySelection normalizeCopySelection(CopyCursor anchor, CopyCursor head) { CopySelection sel; sel.active = true; if (head.row < anchor.row || (head.row == anchor.row && head.col < anchor.col)) { sel.startRow = head.row; sel.startCol = head.col; sel.endRow = anchor.row; sel.endCol = anchor.col; } else { sel.startRow = anchor.row; sel.startCol = anchor.col; sel.endRow = head.row; sel.endCol = head.col; } return sel; } QString copyYankText(const QVector& bufferRows, int bufferBaseRow, const CopySelection& sel) { if (!sel.active || sel.endRow < sel.startRow) { return {}; } QString out; for (int row = sel.startRow; row <= sel.endRow; ++row) { // Rows outside the capture window (selection reaching past it) yield // spaces, so the CELL rectangle is preserved at the fixed width. const int index = row - bufferBaseRow; const QString& line = (index >= 0 && index < bufferRows.size()) ? bufferRows[static_cast(index)] : QString(); const qsizetype width = sel.endCol - sel.startCol + 1; for (qsizetype c = 0; c < width; ++c) { const qsizetype idx = sel.startCol + c; // Blank-pad past a row's painted length (a short line in the rect) // and surrogate halves (a split pair yanks as two spaces rather // than invalid units). if (idx < line.size() && !QChar(line.at(idx)).isHighSurrogate() && !QChar(line.at(idx)).isLowSurrogate()) { out.append(line.at(idx)); } else { out.append(QLatin1Char(' ')); } } if (row != sel.endRow) { out.append(QLatin1Char('\n')); } } return out; }