Skip to content

Wrap last column to terminal width in CLI table formatter#11931

Draft
zachcasper wants to merge 3 commits into
radius-project:mainfrom
zachcasper:line-wrapping
Draft

Wrap last column to terminal width in CLI table formatter#11931
zachcasper wants to merge 3 commits into
radius-project:mainfrom
zachcasper:line-wrapping

Conversation

@zachcasper
Copy link
Copy Markdown
Contributor

@zachcasper zachcasper commented May 18, 2026

Description

Fixes the misaligned/ugly CLI table output when a cell in the last column (typically DESCRIPTION) is longer than the terminal width. The previous text/tabwriter-based renderer let long lines overflow and wrap at the terminal boundary, breaking column alignment on continuation lines.

This PR replaces the table renderer with a custom implementation that:

  • Detects terminal width via golang.org/x/term.
  • Word-wraps content in the last column to fit the remaining width.
  • Pads continuation lines so they sit under the correct column.
  • Is rune-aware (UTF-8 safe) for both column padding and wrapping.
  • Falls back to no wrapping when the writer is not a terminal (e.g., piped output), preserving scriptability.

Example

Before:

$ rad resource-type show Radius.Security/secrets
TYPE                     NAMESPACE
Radius.Security/secrets  Radius.Security

DESCRIPTION:
...
API VERSION: 2025-08-01-preview

TOP-LEVEL PROPERTIES:

NAME         TYPE      REQUIRED  READ-ONLY  DESCRIPTION
application  string    false     false      (Optional) The Radius Application ID. `myApplication.id` for example.
data         object    true      false      (Required) Map of secret data. For example: `data: { username: { value: user1 } password: { value: pass }}`
environment  string    true      false      (Required) The Radius Environment ID. Typically set by the rad CLI. Typically value should be `environment`.
kind         string    false     false      (Optional) The kind of content of the secret. If not specified, generic is assumed. basicAuthentication, awsIRSA, and azureWorkloadIdentity should only be used for configuring authentication to OCI registries for storing Bicep templates. This will change in the future.

OBJECT PROPERTIES:

data

NAME      TYPE      REQUIRED  READ-ONLY  DESCRIPTION
encoding  string    false     false      (Optional) Content encoding of the value. If not specified, `string` is assumed.
value     string    true      false      (Required) The string value of the secret unless encoding is set to 'base64'.

After:

$ rad resource-type show Radius.Security/secrets
TYPE                     NAMESPACE
Radius.Security/secrets  Radius.Security

DESCRIPTION:
...
API VERSION: 2025-08-01-preview

TOP-LEVEL PROPERTIES:

NAME         TYPE      REQUIRED  READ-ONLY  DESCRIPTION
application  string    false     false      (Optional) The Radius Application ID. `myApplication.id` for example.
data         object    true      false      (Required) Map of secret data. For example: `data: { username: { value: user1 } password: { value: pass }}`
environment  string    true      false      (Required) The Radius Environment ID. Typically set by the rad CLI. Typically value should be `environment`.
kind         string    false     false      (Optional) The kind of content of the secret. If not specified, generic is assumed. basicAuthentication, awsIRSA,
                                            and azureWorkloadIdentity should only be used for configuring authentication to OCI registries for storing Bicep
                                            templates. This will change in the future.

OBJECT PROPERTIES:

data

NAME      TYPE      REQUIRED  READ-ONLY  DESCRIPTION
encoding  string    false     false      (Optional) Content encoding of the value. If not specified, `string` is assumed.
value     string    true      false      (Required) The string value of the secret unless encoding is set to 'base64'.

Type of change

  • This pull request fixes a bug in Radius and has an approved issue (issue link required).

Fixes: #9756

Contributor checklist

Please verify that the PR meets the following requirements, where applicable:

  • An overview of proposed schema changes is included in a linked GitHub issue.
    • Not applicable
  • A design document is added or updated under eng/design-notes/ in this repository, if new APIs are being introduced.
    • Not applicable
  • The design document has been reviewed and approved by Radius maintainers/approvers.
    • Not applicable
  • A PR for resource-types-contrib is created, if resource types or recipes are affected by the changes in this PR.
    • Not applicable
  • A PR for dashboard is created, if the Radius Dashboard is affected by the changes in this PR.
    • Not applicable
  • A PR for the documentation repository is created, if the changes in this PR affect the documentation or any user facing updates are made.
    • Not applicable

Copilot AI review requested due to automatic review settings May 18, 2026 19:26
@zachcasper zachcasper requested review from a team as code owners May 18, 2026 19:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the CLI table formatter to wrap long last-column values to the terminal width, improving readability for tables with long descriptions while preserving unwrapped output when terminal width is unknown.

Changes:

  • Replaced text/tabwriter usage with a custom table renderer.
  • Added last-column word wrapping, continuation-line padding, and related tests.
  • Promoted golang.org/x/term to a direct dependency.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
pkg/cli/output/table.go Implements terminal-width detection, custom column sizing, padding, and wrapping helpers.
pkg/cli/output/table_test.go Adds tests for wrapping behavior, unknown-width output, long words, Unicode, and whitespace preservation.
go.mod Marks golang.org/x/term as a direct dependency.

Comment thread pkg/cli/output/table.go Outdated
Comment thread pkg/cli/output/table.go Outdated
zachcasper added a commit to zachcasper/radius that referenced this pull request May 18, 2026
Address Copilot review feedback on radius-project#11931: replace utf8.RuneCountInString
with runewidth.StringWidth so that wide characters (CJK, emoji) and zero-width
combining marks are accounted for correctly in column alignment and last-column
wrapping. Long unbreakable words are now split at display-column boundaries
rather than rune-count boundaries.

- pkg/cli/output/table.go: switch padRight and wordWrap (and the slot/width
  computation) to use runewidth.StringWidth and runewidth.RuneWidth.
- pkg/cli/output/table_test.go: add Test_Table_WrapsWideCharactersByDisplayWidth
  exercising CJK, and update the Unicode wrap expectation to reflect that 🚀
  is two display columns wide.
- go.mod: promote github.com/mattn/go-runewidth to a direct dependency.
Signed-off-by: Zach Casper <zachcasper@microsoft.com>
Address Copilot review feedback on radius-project#11931: replace utf8.RuneCountInString
with runewidth.StringWidth so that wide characters (CJK, emoji) and zero-width
combining marks are accounted for correctly in column alignment and last-column
wrapping. Long unbreakable words are now split at display-column boundaries
rather than rune-count boundaries.

- pkg/cli/output/table.go: switch padRight and wordWrap (and the slot/width
  computation) to use runewidth.StringWidth and runewidth.RuneWidth.
- pkg/cli/output/table_test.go: add Test_Table_WrapsWideCharactersByDisplayWidth
  exercising CJK, and update the Unicode wrap expectation to reflect that 🚀
  is two display columns wide.
- go.mod: promote github.com/mattn/go-runewidth to a direct dependency.

Signed-off-by: Zach Casper <zachcasper@microsoft.com>
Fixes golangci-lint ineffassign: currentWidth is reassigned a few lines later
from the long-word chunking loop, so the intermediate reset to 0 was dead.

Signed-off-by: Zach Casper <zachcasper@microsoft.com>
@radius-functional-tests
Copy link
Copy Markdown

radius-functional-tests Bot commented May 18, 2026

Radius functional test overview

🔍 Go to test action run

Click here to see the test run details
Name Value
Repository zachcasper/radius
Commit ref 466dac2
Unique ID funcbba179128e
Image tag pr-funcbba179128e
  • gotestsum 1.13.0
  • KinD: v0.29.0
  • Dapr: 1.14.4
  • Azure KeyVault CSI driver: 1.4.2
  • Azure Workload identity webhook: 1.3.0
  • Bicep recipe location ghcr.io/radius-project/dev/test/testrecipes/test-bicep-recipes/<name>:pr-funcbba179128e
  • Terraform recipe location http://tf-module-server.radius-test-tf-module-server.svc.cluster.local/<name>.zip (in cluster)
  • applications-rp test image location: ghcr.io/radius-project/dev/applications-rp:pr-funcbba179128e
  • dynamic-rp test image location: ghcr.io/radius-project/dev/dynamic-rp:pr-funcbba179128e
  • controller test image location: ghcr.io/radius-project/dev/controller:pr-funcbba179128e
  • ucp test image location: ghcr.io/radius-project/dev/ucpd:pr-funcbba179128e
  • deployment-engine test image location: ghcr.io/radius-project/deployment-engine:latest

Test Status

❌ Container images build failed
❌ Test recipe publishing failed
✅ Recipe publishing succeeded
⌛ Starting ucp-cloud functional tests...
⌛ Starting corerp-cloud functional tests...
✅ corerp-cloud functional tests succeeded
✅ ucp-cloud functional tests succeeded
✅ corerp-cloud functional tests succeeded
✅ corerp-cloud functional tests succeeded

@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 83.82353% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.81%. Comparing base (72a582d) to head (466dac2).

Files with missing lines Patch % Lines
pkg/cli/output/table.go 83.82% 12 Missing and 10 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11931      +/-   ##
==========================================
+ Coverage   51.72%   51.81%   +0.09%     
==========================================
  Files         726      726              
  Lines       45608    45711     +103     
==========================================
+ Hits        23591    23686      +95     
- Misses      19793    19800       +7     
- Partials     2224     2225       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect line wrapping for property description in rad resource-type show

2 participants