1package headless23import (4 "src.rest/cat/012/internal/sheet"5)67// Range is a range's values as a grid, for agents: what ReadRange8// returns and the MCP read_range tool gives back. Its JSON form is a9// stable schema (docs/reference/json.md).10type Range struct {11 // Range is what was read, with its sheet: Q3!A1:D9; a whole sheet is12 // read from A1 to its last cell with contents.13 Range string `json:"range"`14 Rows int `json:"rows"`15 Cols int `json:"cols"`16 // Values are the cells row by row, each a Value typed by its format:17 // numbers, strings, true or false, null for blank, {"currency": 3.5},18 // {"date": "2026-09-29"} and the rest, errors as their text (#DIV/0!).19 Values [][]Value `json:"values"`20 // Text is each cell as the sheet shows it, unless left out.21 Text [][]string `json:"text,omitempty"`22 // Formulas are the formulas in the range by cell (B7: =SUM(B1:B6)).23 Formulas map[string]string `json:"formulas"`24 // Truncated is set when the range held more than the most cells a25 // read returns; Rows says how many rows came back.26 Truncated bool `json:"truncated"`27}2829// ReadOptions tune ReadRange.30type ReadOptions struct {31 MaxCells int // the most cells to return, whole rows at a time; 0 is 10,00032 NoText bool // leave out each cell as shown33}3435// ReadRange reads t's cells as a grid.36func ReadRange(t Target, o ReadOptions) Range {37 if o.MaxCells <= 0 {38 o.MaxCells = 10_00039 }40 s, r := t.Sheet, t.Range41 if t.Whole {42 r, _ = s.UsedRange()43 } else if data, ok := s.FilledBounds(r); ok {44 r.To.Row, r.To.Col = min(r.To.Row, data.To.Row), min(r.To.Col, data.To.Col)45 } else {46 r.To = r.From47 }48 cols := r.To.Col - r.From.Col + 149 rows := r.To.Row - r.From.Row + 150 out := Range{Range: sheet.Qualified(s.Name(), r), Cols: cols, Formulas: map[string]string{}}51 if rows*cols > o.MaxCells {52 rows, out.Truncated = max(o.MaxCells/cols, 1), true53 }54 out.Rows = rows55 for row := r.From.Row; row < r.From.Row+rows; row++ {56 vals := make([]Value, cols)57 var text []string58 if !o.NoText {59 text = make([]string, cols)60 }61 for i := range cols {62 a := sheet.Addr{Col: r.From.Col + i, Row: row}63 vals[i] = Value("null")64 if c := s.Cell(a); c != nil && c.IsFormula() {65 out.Formulas[a.String()] = c.Input66 }67 if s.Value(a).Kind == sheet.Empty {68 continue69 }70 vals[i] = CellValue(s, a)71 if text != nil {72 text[i] = s.LocalText(a)73 }74 }75 out.Values = append(out.Values, vals)76 if text != nil {77 out.Text = append(out.Text, text)78 }79 }80 return out81}