Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/officecli/CommandBuilder.View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ private static Command BuildViewCommand(Option<bool> jsonOption)
}
}
else if (handler is OfficeCli.Handlers.ExcelHandler excelHandler)
html = RenderViaRegistry(excelHandler, "xlsx", new OfficeCli.Core.Rendering.RenderOptions())!;
html = RenderViaRegistry(excelHandler, "xlsx", new OfficeCli.Core.Rendering.RenderOptions { CellRange = clipArg })!;
else if (handler is OfficeCli.Handlers.WordHandler wordHandlerGrid && gridCols != 0)
{
// Contact-sheet grid: tile every page into an N-column (or auto)
Expand Down
5 changes: 5 additions & 0 deletions src/officecli/Core/Rendering/RenderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ public sealed class RenderOptions

/// <summary>Target raster height in px for Png/Pdf output. 0 = renderer default.</summary>
public int RasterHeightPx { get; init; }

/// <summary>Xlsx cell range ("Sheet1!A1:J20" or "/Sheet1/A1:J20") whose cells must
/// exist in the rendered grid, so a screenshot --range crop can resolve corner
/// cells beyond the used range. Null = used range only. Non-Excel renderers ignore it.</summary>
public string? CellRange { get; init; }
}
22 changes: 19 additions & 3 deletions src/officecli/Handlers/Excel/ExcelHandler.HtmlPreview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,12 @@ private string[] GetResolvedIndexedColors()
/// Supports cell formatting (font, fill, borders, alignment), merged cells,
/// column widths, row heights, frozen panes, and sheet tab switching.
/// </summary>
public string ViewAsHtml()
/// <param name="ensureRange">Optional cell range ("Sheet1!A1:J20" or "/Sheet1/A1:J20").
/// The named sheet's grid is extended to cover it, so screenshot --range corner
/// cells beyond the used range still resolve to real grid cells.</param>
public string ViewAsHtml(string? ensureRange = null)
{
var ensure = ParseViewRange(ensureRange, ignoreInvalid: true);
using var _cul = InvariantCultureScope.Enter();
var sb = new StringBuilder();
var sheets = GetWorksheets();
Expand Down Expand Up @@ -242,7 +246,9 @@ public string ViewAsHtml()
var pictures = CollectSheetPictures(worksheetPart);
if (pictures.Count > 0)
charts.AddRange(pictures);
RenderSheetTable(sb, sheetName, renderPart, stylesheet, renderStyles, charts, sheetIdx, showGridLines);
var ensureCell = ensure != null && string.Equals(ensure.Value.Sheet, sheetName, StringComparison.OrdinalIgnoreCase)
? (ensure.Value.R2, ensure.Value.C2) : ((int, int)?)null;
RenderSheetTable(sb, sheetName, renderPart, stylesheet, renderStyles, charts, sheetIdx, showGridLines, ensureCell);
sb.AppendLine("</div>");
}
sb.AppendLine("</div>");
Expand Down Expand Up @@ -307,11 +313,12 @@ public int GetSheetIndex(string sheetName)
return -1;
}


// ==================== Sheet Rendering ====================

private void RenderSheetTable(StringBuilder sb, string sheetName, WorksheetPart worksheetPart, Stylesheet? stylesheet, RenderStyleArrays renderStyles,
List<(int fromRow, int toRow, int fromCol, int toCol, double colOffsetPt, string html)>? charts = null, int sheetIdx = 0,
bool showGridLines = true)
bool showGridLines = true, (int Row, int Col)? ensureCell = null)
{
var ws = GetSheet(worksheetPart);
var sheetData = ws.GetFirstChild<SheetData>();
Expand Down Expand Up @@ -463,6 +470,15 @@ private void RenderSheetTable(StringBuilder sb, string sheetName, WorksheetPart
if (toCol > maxCol) maxCol = toCol;
if (toRow > maxRow) maxRow = toRow;
}
// Extend maxRow/maxCol to cover an explicitly requested screenshot range
// (issue #246): its corner cells must exist in the grid or the clip-crop
// union collapses to whichever corner is inside the used range. Subject
// to the same render caps below.
if (ensureCell != null)
{
if (ensureCell.Value.Row > maxRow) maxRow = ensureCell.Value.Row;
if (ensureCell.Value.Col > maxCol) maxCol = ensureCell.Value.Col;
}

// Column cap: >200 cols is unusable in a browser table regardless of rendering mode.
// Row cap: default 5000; overridable via OnGetHtmlRowCap when the rendering backend
Expand Down
11 changes: 7 additions & 4 deletions src/officecli/Handlers/Excel/ExcelHandler.View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ public string ViewAsText(int? startLine = null, int? endLine = null, int? maxLin
}

/// <summary>
/// Parse a `view text --range` target ('Sheet1!A1:C10', '/Sheet1/A1:C10',
/// Parse an xlsx cell-range target ('Sheet1!A1:C10', '/Sheet1/A1:C10',
/// or a single cell 'Sheet1!B5') into an inclusive 1-based rectangle.
/// Corner order is normalized (C10:A1 works). The sheet must exist —
/// unknown names throw not_found listing the available sheets, mirroring
/// screenshot mode's range_target_not_found actionability.
/// unknown names throw not_found listing the available sheets.
/// Set <paramref name="ignoreInvalid"/> for callers that also accept non-cell paths.
/// </summary>
private (string Sheet, int R1, int C1, int R2, int C2)? ParseViewRange(string? range)
private (string Sheet, int R1, int C1, int R2, int C2)? ParseViewRange(string? range, bool ignoreInvalid = false)
{
if (string.IsNullOrWhiteSpace(range)) return null;
var c = range.Trim();
Expand All @@ -96,9 +96,12 @@ public string ViewAsText(int? startLine = null, int? endLine = null, int? maxLin
c = "/" + c[..bang] + "/" + c[(bang + 1)..];
var m = Regex.Match(c, @"^/([^/]+)/([A-Za-z]{1,3}\d+)(?::([A-Za-z]{1,3}\d+))?$");
if (!m.Success)
{
if (ignoreInvalid) return null;
throw new Core.CliException(
$"Invalid --range '{range}'. Expected 'Sheet1!A1:C10', '/Sheet1/A1:C10', or a single cell 'Sheet1!B5'.")
{ Code = "invalid_value" };
}

var sheet = m.Groups[1].Value;
var names = GetWorksheets().Select(s => s.Item1).ToList();
Expand Down
2 changes: 1 addition & 1 deletion src/officecli/Handlers/Rendering/BasicRenderers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public RenderResult Render(IRenderInput input, RenderOptions options)
{
var h = (input as HandlerRenderInput)?.Handler as ExcelHandler
?? throw new InvalidOperationException("ExcelHandler render input expected");
return RenderResult.Html(h.ViewAsHtml());
return RenderResult.Html(h.ViewAsHtml(options.CellRange));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/officecli/ResidentServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,7 @@ private void ExecuteView(ResidentRequest req, OutputFormat format)
}
}
else if (_handler is OfficeCli.Handlers.ExcelHandler excelShotHandler)
html = CommandBuilder.RenderViaRegistry(excelShotHandler, "xlsx", new OfficeCli.Core.Rendering.RenderOptions())!;
html = CommandBuilder.RenderViaRegistry(excelShotHandler, "xlsx", new OfficeCli.Core.Rendering.RenderOptions { CellRange = rangeArg })!;
else if (_handler is OfficeCli.Handlers.WordHandler wordShotGrid && gridCols != 0)
{
// Contact-sheet grid — mirrors CommandBuilder.View.cs's docx grid
Expand Down