// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . package review import ( "strings" "git.stormux.org/storm/fenrir/internal/i18n" "git.stormux.org/storm/fenrir/internal/presentation" "git.stormux.org/storm/fenrir/internal/screen" ) func (r *Review) Set_mark() presentation.Message { if !r.ready || !r.reviewing { return presentation.Message{Text: i18n.T("no review cursor")} } if r.tableMode { return r.set_table_headers() } point := r.y*r.screen.Cols + r.x cue := presentation.PlaceStartMark if r.markCount == 0 { r.startMark, r.markCount = point, 1 } else { r.endMark, r.markCount = point, 2 cue = presentation.PlaceEndMark } return presentation.Message{Text: i18n.T("set mark"), Cue: cue} } func (r *Review) Clear_marks() { r.markCount = 0 if r.tableMode { r.tableHeaders = nil r.tableLayout = tableLayout{} r.tableManual = false r.tableAutoDisabled = true } } // Marked_region is rectangular, unlike clipboard selection. A single mark // optionally selects the rest of its physical row. Table header marks never // enter markCount and cannot accidentally become reading regions. func (r *Review) Marked_region(singleLine bool) (screen.Region, bool) { if !r.ready || r.markCount == 0 || r.markCount < 2 && !singleLine { return screen.Region{}, false } x1, y1 := r.startMark%r.screen.Cols, r.startMark/r.screen.Cols if r.markCount == 1 { return screen.Region{Left: x1, Top: y1, Bottom: y1, ToLineEnd: true}, true } x2, y2 := r.endMark%r.screen.Cols, r.endMark/r.screen.Cols return screen.Region{Left: min(x1, x2), Top: min(y1, y2), Right: max(x1, x2), Bottom: max(y1, y2)}, true } // Marked_text uses inclusive Unicode cell positions in either direction. Copy // with one mark takes the current review cursor as its second endpoint. func (r *Review) Marked_text(copying bool) (string, bool) { if !r.ready || r.markCount == 0 || !copying && r.markCount < 2 { return "", false } end := r.endMark if r.markCount == 1 { end = r.y*r.screen.Cols + r.x } start := r.startMark if start > end { start, end = end, start } var lines []string for row := start / r.screen.Cols; row <= end/r.screen.Cols; row++ { left, right := 0, r.screen.Cols if row == start/r.screen.Cols { left = start % r.screen.Cols } if row == end/r.screen.Cols { right = end%r.screen.Cols + 1 } text := string(r.screen.Line(row)[left:right]) if start/r.screen.Cols != end/r.screen.Cols { text = strings.TrimRight(text, " \x00\t\r") } lines = append(lines, text) } return strings.Join(lines, "\n"), true }