diff --git a/pkg/cmd/codespace/view.go b/pkg/cmd/codespace/view.go index 11f772b71..7fe4ae313 100644 --- a/pkg/cmd/codespace/view.go +++ b/pkg/cmd/codespace/view.go @@ -7,8 +7,9 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/codespaces/api" + "github.com/cli/cli/v2/internal/tableprinter" + "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/cmdutil" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -27,26 +28,12 @@ func newViewCmd(app *App) *cobra.Command { viewCmd := &cobra.Command{ Use: "view", Short: "View details about a codespace", - Long: heredoc.Doc(` - View details about a codespace. - - For more fine-grained details, you can add the "--json" flag to view the full list of fields available. - - If this command doesn't provide enough information, you can use the "gh api" command to view the full JSON response: - $ gh api /user/codespaces/ - `), Example: heredoc.Doc(` # select a codespace from a list of all codespaces you own $ gh cs view # view the details of a specific codespace - $ gh cs view -c - - # select a codespace from a list of codespaces in a repository - $ gh cs view --repo / - - # select a codespace from a list of codespaces with a specific repository owner - $ gh cs view --repo-owner + $ gh cs view -c codespace-name-12345 # view the list of all available fields for a codespace $ gh cs view --json @@ -86,8 +73,7 @@ func (a *App) ViewCodespace(ctx context.Context, opts *viewOptions) error { return opts.exporter.Write(a.io, selectedCodespace) } - //nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter - tp := utils.NewTablePrinter(a.io) + tp := tableprinter.New(a.io) c := codespace{selectedCodespace} formattedName := formatNameForVSCSTarget(c.Name, c.VSCSTarget) @@ -108,10 +94,10 @@ func (a *App) ViewCodespace(ctx context.Context, opts *viewOptions) error { } for _, field := range fields { - // Only display the field if it has a value. - if field.value != "" { - tp.AddField(field.name, nil, nil) - tp.AddField(field.value, nil, nil) + // Don't display the field if it is empty and we are printing to a TTY + if !a.io.IsStdoutTTY() || field.value != "" { + tp.AddField(field.name) + tp.AddField(field.value) tp.EndRow() } } @@ -126,8 +112,12 @@ func (a *App) ViewCodespace(ctx context.Context, opts *viewOptions) error { func formatGitStatus(codespace codespace) string { branchWithGitStatus := codespace.branchWithGitStatus() - // u2193 and u2191 are the unicode arrows for down and up - return fmt.Sprintf("%s - %d\u2193 %d\u2191", branchWithGitStatus, codespace.GitStatus.Behind, codespace.GitStatus.Ahead) + + // Format the commits ahead/behind with proper pluralization + commitsAhead := text.Pluralize(codespace.GitStatus.Ahead, "commit") + commitsBehind := text.Pluralize(codespace.GitStatus.Behind, "commit") + + return fmt.Sprintf("%s - %s ahead, %s behind", branchWithGitStatus, commitsAhead, commitsBehind) } func formatRetentionPeriodDays(codespace codespace) string { diff --git a/pkg/cmd/codespace/view_test.go b/pkg/cmd/codespace/view_test.go index edcfefef6..bba487232 100644 --- a/pkg/cmd/codespace/view_test.go +++ b/pkg/cmd/codespace/view_test.go @@ -37,14 +37,14 @@ func Test_NewCmdView(t *testing.T) { codespaceName: "monalisa-cli-cli-abcdef", opts: &viewOptions{}, wantErr: false, - wantStdout: "Name\tmonalisa-cli-cli-abcdef\nGit Status\t - 0↓ 0↑\nIdle Timeout\t0 minutes\n", + wantStdout: "Name\tmonalisa-cli-cli-abcdef\nState\t\nRepository\t\nGit Status\t - 0 commits ahead, 0 commits behind\nDevcontainer Path\t\nMachine Display Name\t\nIdle Timeout\t0 minutes\nCreated At\t\nRetention Period\t\n", }, { tName: "command succeeds because codespace exists (with details)", codespaceName: "monalisa-cli-cli-hijklm", opts: &viewOptions{}, wantErr: false, - wantStdout: "Name\tmonalisa-cli-cli-hijklm\nGit Status\tmain* - 2↓ 1↑\nIdle Timeout\t30 minutes\nRetention Period\t1 day\n", + wantStdout: "Name\tmonalisa-cli-cli-hijklm\nState\tAvailable\nRepository\tcli/cli\nGit Status\tmain* - 1 commit ahead, 2 commits behind\nDevcontainer Path\t.devcontainer/devcontainer.json\nMachine Display Name\tTest Display Name\nIdle Timeout\t30 minutes\nCreated At\t\nRetention Period\t1 day\n", }, } @@ -107,6 +107,12 @@ func testViewApiMock() *apiClientMock { }, IdleTimeoutMinutes: 30, RetentionPeriodMinutes: 1440, + State: "Available", + Repository: api.Repository{FullName: "cli/cli"}, + DevContainerPath: ".devcontainer/devcontainer.json", + Machine: api.CodespaceMachine{ + DisplayName: "Test Display Name", + }, } return &apiClientMock{ GetCodespaceFunc: func(_ context.Context, name string, _ bool) (*api.Codespace, error) {