mirror of https://github.com/pulumi/pulumi.git
130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
// Copyright 2016-2018, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build !js
|
|
// +build !js
|
|
|
|
package cmdutil
|
|
|
|
import (
|
|
"io"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
)
|
|
|
|
func readConsoleFancy(stdout io.Writer, stdin io.Reader, prompt string, secret bool) (string, error) {
|
|
final, err := tea.NewProgram(
|
|
newReadConsoleModel(prompt, secret),
|
|
tea.WithInput(stdin),
|
|
tea.WithOutput(stdout),
|
|
).Run()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
model, ok := final.(readConsoleModel)
|
|
contract.Assertf(ok, "expected readConsoleModel, got %T", final)
|
|
if model.Canceled {
|
|
return "", io.EOF
|
|
}
|
|
|
|
return model.Value, nil
|
|
}
|
|
|
|
// readConsoleModel drives a bubbletea widget that reads from the console.
|
|
type readConsoleModel struct {
|
|
input textinput.Model
|
|
secret bool
|
|
|
|
// Canceled is set to true when the model finishes
|
|
// if the user canceled the operation by pressing Ctrl-C or Esc.
|
|
Canceled bool
|
|
|
|
// Value is the user's response to the prompt.
|
|
Value string
|
|
}
|
|
|
|
var _ tea.Model = readConsoleModel{}
|
|
|
|
func newReadConsoleModel(prompt string, secret bool) readConsoleModel {
|
|
input := textinput.New()
|
|
input.Cursor.Style = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("205")) // 205 = hot pink cursor
|
|
if secret {
|
|
input.EchoMode = textinput.EchoPassword
|
|
}
|
|
if prompt != "" {
|
|
input.Prompt = prompt + ": "
|
|
}
|
|
input.Focus() // required to receive input
|
|
|
|
return readConsoleModel{
|
|
input: input,
|
|
secret: secret,
|
|
}
|
|
}
|
|
|
|
// Init initializes the model.
|
|
// We don't have any initialization to do, so we just return nil.
|
|
func (readConsoleModel) Init() tea.Cmd { return nil }
|
|
|
|
// Update handles a single tick of the bubbletea loop.
|
|
func (m readConsoleModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
// If the user pressed enter, Ctrl-C, or Esc,
|
|
// it's time to stop the bubbletea loop.
|
|
//
|
|
// Only Enter is considered a success.
|
|
//nolint:exhaustive // We only want special handling for these keys.
|
|
switch msg.Type {
|
|
case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
|
|
m.Value = m.input.Value()
|
|
m.Canceled = msg.Type != tea.KeyEnter
|
|
|
|
m.input.Blur() // hide the cursor
|
|
if m.secret {
|
|
// If we're in secret mode, don't include
|
|
// the '*' characters in the final output
|
|
// so as not to leak the length of the input.
|
|
m.input.EchoMode = textinput.EchoNone
|
|
}
|
|
|
|
var cmds []tea.Cmd
|
|
if !m.Canceled {
|
|
// If the user accepts the input,
|
|
// we'll primnt the prompt to the terminal
|
|
// before exiting this loop.
|
|
cmds = append(cmds, tea.Println(m.input.View()))
|
|
}
|
|
cmds = append(cmds, tea.Quit)
|
|
|
|
return m, tea.Sequence(cmds...)
|
|
}
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
m.input, cmd = m.input.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
// View renders the prompt.
|
|
func (m readConsoleModel) View() string {
|
|
return m.input.View()
|
|
}
|