Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[color] Use graphemes to measure strings. #11202

Merged
merged 2 commits into from Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: cli/display
description: Fix text cutting off prior to the edge of the terminal
4 changes: 1 addition & 3 deletions pkg/backend/display/jsonmessage.go
Expand Up @@ -193,9 +193,7 @@ func (r *messageRenderer) tick(display *ProgressDisplay) {
func (r *messageRenderer) renderRow(display *ProgressDisplay,
id string, colorizedColumns []string, maxColumnLengths []int) {

uncolorizedColumns := display.uncolorizeColumns(colorizedColumns)

row := renderRow(colorizedColumns, uncolorizedColumns, maxColumnLengths)
row := renderRow(colorizedColumns, maxColumnLengths)
if r.isInteractive {
// Ensure we don't go past the end of the terminal. Note: this is made complex due to
// msgWithColors having the color code information embedded with it. So we need to get
Expand Down
57 changes: 15 additions & 42 deletions pkg/backend/display/progress.go
Expand Up @@ -25,7 +25,6 @@ import (
"strings"
"time"
"unicode"
"unicode/utf8"

"github.com/pulumi/pulumi/pkg/v3/backend/display/internal/terminal"
"github.com/pulumi/pulumi/pkg/v3/engine"
Expand Down Expand Up @@ -138,10 +137,6 @@ type ProgressDisplay struct {
// Maps used so we can generate short IDs for resource urns.
urnToID map[resource.URN]string

// Cache of colorized to uncolorized text. We go between the two a lot, so caching helps
// prevent lots of recomputation
colorizedToUncolorized map[string]string

// Structure that tracks the time taken to perform an action on a resource.
opStopwatch opStopwatch
}
Expand Down Expand Up @@ -260,20 +255,19 @@ func ShowProgressEvents(op string, action apitype.UpdateKind, stack tokens.Name,
}

display := &ProgressDisplay{
action: action,
isPreview: isPreview,
isTerminal: isInteractive,
opts: opts,
renderer: renderer,
stack: stack,
proj: proj,
eventUrnToResourceRow: make(map[resource.URN]ResourceRow),
suffixColumn: int(statusColumn),
suffixesArray: []string{"", ".", "..", "..."},
urnToID: make(map[resource.URN]string),
colorizedToUncolorized: make(map[string]string),
displayOrderCounter: 1,
opStopwatch: newOpStopwatch(),
action: action,
isPreview: isPreview,
isTerminal: isInteractive,
opts: opts,
renderer: renderer,
stack: stack,
proj: proj,
eventUrnToResourceRow: make(map[resource.URN]ResourceRow),
suffixColumn: int(statusColumn),
suffixesArray: []string{"", ".", "..", "..."},
urnToID: make(map[resource.URN]string),
displayOrderCounter: 1,
opStopwatch: newOpStopwatch(),
}

ticker := time.NewTicker(1 * time.Second)
Expand All @@ -289,27 +283,7 @@ func ShowProgressEvents(op string, action apitype.UpdateKind, stack tokens.Name,
}

func (display *ProgressDisplay) println(line string) {
display.renderer.println(display, display.opts.Color.Colorize(line))
}

func (display *ProgressDisplay) uncolorizeString(v string) string {
uncolorized, has := display.colorizedToUncolorized[v]
if !has {
uncolorized = colors.Never.Colorize(v)
display.colorizedToUncolorized[v] = uncolorized
}

return uncolorized
}

func (display *ProgressDisplay) uncolorizeColumns(columns []string) []string {
uncolorizedColumns := make([]string, len(columns))

for i, v := range columns {
uncolorizedColumns[i] = display.uncolorizeString(v)
}

return uncolorizedColumns
display.renderer.println(display, line)
}

type treeNode struct {
Expand Down Expand Up @@ -415,10 +389,9 @@ func (display *ProgressDisplay) convertNodesToRows(
}

colorizedColumns := make([]string, len(node.colorizedColumns))
uncolorisedColumns := display.uncolorizeColumns(node.colorizedColumns)

for i, colorizedColumn := range node.colorizedColumns {
columnWidth := utf8.RuneCountInString(uncolorisedColumns[i])
columnWidth := colors.MeasureColorizedString(colorizedColumn)

if i == display.suffixColumn {
columnWidth += maxSuffixLength
Expand Down
25 changes: 12 additions & 13 deletions pkg/backend/display/tableutil.go
Expand Up @@ -16,7 +16,6 @@ package display

import (
"strings"
"unicode/utf8"

"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
Expand All @@ -26,9 +25,9 @@ func columnHeader(msg string) string {
return colors.Underline + colors.BrightBlue + msg + colors.Reset
}

func messagePadding(message string, maxLength, extraPadding int) string {
extraWhitespace := maxLength - utf8.RuneCountInString(message)
contract.Assertf(extraWhitespace >= 0, "Neg whitespace. %v %s", maxLength, message)
func messagePadding(message string, maxWidth, extraPadding int) string {
extraWhitespace := maxWidth - colors.MeasureColorizedString(message)
contract.Assertf(extraWhitespace >= 0, "Neg whitespace. %v %s", maxWidth, message)

// Place two spaces between all columns (except after the first column). The first
// column already has a ": " so it doesn't need the extra space.
Expand All @@ -38,12 +37,12 @@ func messagePadding(message string, maxLength, extraPadding int) string {
}

// Gets the padding necessary to prepend to a column in order to keep it aligned in the terminal.
func columnPadding(uncolorizedColumns []string, columnIndex int, maxColumnLengths []int) string {
func columnPadding(columns []string, columnIndex int, maxColumnWidths []int) string {
extraWhitespace := " "
if columnIndex >= 0 && len(maxColumnLengths) > 0 {
column := uncolorizedColumns[columnIndex]
maxLength := maxColumnLengths[columnIndex]
extraWhitespace = messagePadding(column, maxLength, 2)
if columnIndex >= 0 && len(maxColumnWidths) > 0 {
column := columns[columnIndex]
maxWidth := maxColumnWidths[columnIndex]
extraWhitespace = messagePadding(column, maxWidth, 2)
}
return extraWhitespace
}
Expand All @@ -52,11 +51,11 @@ func columnPadding(uncolorizedColumns []string, columnIndex int, maxColumnLength
// status, then some amount of optional padding, then some amount of msgWithColors, then the
// suffix. Importantly, if there isn't enough room to display all of that on the terminal, then
// the msg will be truncated to try to make it fit.
func renderRow(colorizedColumns, uncolorizedColumns []string, maxColumnLengths []int) string {
func renderRow(columns []string, maxColumnWidths []int) string {
var row strings.Builder
for i := 0; i < len(colorizedColumns); i++ {
row.WriteString(columnPadding(uncolorizedColumns, i-1, maxColumnLengths))
row.WriteString(colorizedColumns[i])
for i := 0; i < len(columns); i++ {
row.WriteString(columnPadding(columns, i-1, maxColumnWidths))
row.WriteString(columns[i])
}
return row.String()
}