Skip to content

Commit

Permalink
IMPROVEMENT-24: In progress
Browse files Browse the repository at this point in the history
  • Loading branch information
grafviktor committed Dec 28, 2023
1 parent 87190b4 commit 7803c4f
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 53 deletions.
148 changes: 102 additions & 46 deletions internal/ui/component/edithost/edit_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ type (
MsgSave struct{}
)

const (
inputTitle int = iota
inputAddress
inputDescription
inputLogin
inputNetworkPort
inputIdentityFile
)

// ItemID is a key to extract item id from application context.
var ItemID = struct{}{}

Expand All @@ -55,7 +64,9 @@ func networkPortValidator(s string) error {
return nil
}

Check warning on line 65 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L62-L65

Added lines #L62 - L65 were not covered by tests

if num, err := strconv.ParseInt(s, 10, 16); err != nil || num < 1 {
auto := 0 // 0 is used to autodetect base, see strconv.ParseUint
maxLengthBit := 16
if num, err := strconv.ParseUint(s, auto, maxLengthBit); err != nil || num < 1 {
return fmt.Errorf("network port must be a number which is less than 65 535")
}

Check warning on line 71 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L67-L71

Added lines #L67 - L71 were not covered by tests

Expand All @@ -64,56 +75,64 @@ func networkPortValidator(s string) error {

// New - returns new edit host form.
func New(ctx context.Context, storage storage.HostStorage, state *state.ApplicationState, log logger) editModel {
initialFocusedInput := inputTitle
isNewHost := false

Check warning on line 80 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L78-L80

Added lines #L78 - L80 were not covered by tests
// if we can't cast host id to int, that means we're adding a new host. Ignoring the error
hostID, _ := ctx.Value(ItemID).(int)
host, err := storage.Get(hostID)
if err != nil {
host = model.Host{}
initialFocusedInput = inputAddress
isNewHost = true

Check warning on line 87 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L86-L87

Added lines #L86 - L87 were not covered by tests
}

m := editModel{
inputs: make([]labeledInput, 6),
hostStorage: storage,
host: host,
help: help.New(),
keyMap: keys,
appState: state,
logger: log,
inputs: make([]labeledInput, 6),
hostStorage: storage,
host: host,
help: help.New(),
keyMap: keys,
appState: state,
logger: log,
focusedInput: initialFocusedInput,
isNewHost: isNewHost,

Check warning on line 99 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L91-L99

Added lines #L91 - L99 were not covered by tests
}

var t labeledInput
for i := range m.inputs {
t = NewLabelInput()
t.Cursor.Style = cursorStyle
t.Placeholder = "n/a"

switch i {
case 0:
case inputTitle:

Check warning on line 108 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L108

Added line #L108 was not covered by tests
t.Label = "Title"
t.Focus()
t.SetValue(host.Title)
t.Placeholder = "title"
t.Validate = notEmptyValidator
case 1:
case inputAddress:

Check warning on line 113 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L111-L113

Added lines #L111 - L113 were not covered by tests
t.Label = "IP Address or Hostname"
t.CharLimit = 128
t.SetValue(host.Address)
t.Placeholder = "address"
t.Validate = notEmptyValidator
case 2:
case inputDescription:

Check warning on line 119 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L117-L119

Added lines #L117 - L119 were not covered by tests
t.Label = "Description"
t.CharLimit = 512
t.Placeholder = "n/a"

Check warning on line 122 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L122

Added line #L122 was not covered by tests
t.SetValue(host.Description)
case 3:
case inputLogin:

Check warning on line 124 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L124

Added line #L124 was not covered by tests
t.Label = "Login"
t.CharLimit = 128
t.Placeholder = fmt.Sprintf("default: %s", utils.CurrentUsername())
t.SetValue(host.LoginName)
case 4:
t.Label = "Port"
case inputNetworkPort:
t.Label = "Network port"

Check warning on line 130 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L129-L130

Added lines #L129 - L130 were not covered by tests
t.CharLimit = 5
t.Placeholder = "default: 22"
t.SetValue(host.RemotePort)
t.Validate = networkPortValidator
case 5:
case inputIdentityFile:

Check warning on line 135 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L134-L135

Added lines #L134 - L135 were not covered by tests
t.Label = "Identity file path"
t.CharLimit = 512
t.Placeholder = "default: $HOME/.ssh/id_rsa"
Expand All @@ -123,20 +142,23 @@ func New(ctx context.Context, storage storage.HostStorage, state *state.Applicat
m.inputs[i] = t
}

m.inputs[m.focusedInput].Focus()

Check warning on line 146 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L145-L146

Added lines #L145 - L146 were not covered by tests
return m
}

type editModel struct {
keyMap keyMap
hostStorage storage.HostStorage
focusIndex int
inputs []labeledInput
host model.Host
viewport viewport.Model
help help.Model
ready bool
appState *state.ApplicationState
logger logger
keyMap keyMap
hostStorage storage.HostStorage
focusedInput int
inputs []labeledInput
host model.Host
isNewHost bool
viewport viewport.Model
help help.Model
ready bool
appState *state.ApplicationState
logger logger
}

func (m editModel) Init() tea.Cmd {
Expand Down Expand Up @@ -164,8 +186,7 @@ func (m editModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keyMap.Save):
m, cmd = m.save(msg)
cmds = append(cmds, cmd)
case key.Matches(msg, m.keyMap.Down) ||
key.Matches(msg, m.keyMap.Up):
case key.Matches(msg, m.keyMap.Down) || key.Matches(msg, m.keyMap.Up):

Check warning on line 189 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L189

Added line #L189 was not covered by tests
m, cmd = m.inputFocusChange(msg)
cmds = append(cmds, cmd)
}
Expand All @@ -188,17 +209,17 @@ func (m editModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m editModel) save(_ tea.Msg) (editModel, tea.Cmd) {
for i := range m.inputs {
switch i {
case 0:
case inputTitle:

Check warning on line 212 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L212

Added line #L212 was not covered by tests
m.host.Title = m.inputs[i].Value()
case 1:
case inputAddress:

Check warning on line 214 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L214

Added line #L214 was not covered by tests
m.host.Address = m.inputs[i].Value()
case 2:
case inputDescription:

Check warning on line 216 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L216

Added line #L216 was not covered by tests
m.host.Description = m.inputs[i].Value()
case 3:
case inputLogin:

Check warning on line 218 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L218

Added line #L218 was not covered by tests
m.host.LoginName = m.inputs[i].Value()
case 4:
case inputNetworkPort:

Check warning on line 220 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L220

Added line #L220 was not covered by tests
m.host.RemotePort = m.inputs[i].Value()
case 5:
case inputIdentityFile:

Check warning on line 222 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L222

Added line #L222 was not covered by tests
m.host.PrivateKeyPath = m.inputs[i].Value()
}
}
Expand All @@ -210,9 +231,41 @@ func (m editModel) save(_ tea.Msg) (editModel, tea.Cmd) {
)
}

func (m editModel) copyAddressToTitle() {
newValue := m.inputs[inputAddress].Value()

// Temprorary remove input validator.
// It's necessary, because input.SetValue(...) invokes Validate function,
// if the input contains invalid value, Validate function returns error and
// rejects new value. That leads to a problem - when user removes all symbols
// from address input, title input still preserves the very last letter.
// A better way would be to use own validation logic instead of relying
// on input.Validate.
validator := m.inputs[inputTitle].Validate
m.inputs[inputTitle].Validate = nil
m.inputs[inputTitle].SetValue(newValue)
m.inputs[inputTitle].SetCursor(len(newValue))
m.inputs[inputTitle].Validate = validator
m.inputs[inputTitle].Err = m.inputs[inputTitle].Validate(newValue)

Check warning on line 249 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L234-L249

Added lines #L234 - L249 were not covered by tests
}

func (m editModel) focusedInputProcessKeyEvent(msg tea.Msg) (editModel, tea.Cmd) {
var cmd tea.Cmd
m.inputs[m.focusIndex], cmd = m.inputs[m.focusIndex].Update(msg)
var shouldUpdateTitle bool

// Decide if we need to propagate hostname to title
if m.focusedInput == inputAddress {
addressEqualsTitle := m.inputs[inputAddress].Value() == m.inputs[inputTitle].Value()
shouldUpdateTitle = m.isNewHost && addressEqualsTitle
}

Check warning on line 260 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L254-L260

Added lines #L254 - L260 were not covered by tests

// Update focused input
m.inputs[m.focusedInput], cmd = m.inputs[m.focusedInput].Update(msg)

// Then, update title if we should
if shouldUpdateTitle {
m.copyAddressToTitle()
}

Check warning on line 268 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L263-L268

Added lines #L263 - L268 were not covered by tests

return m, cmd
}
Expand Down Expand Up @@ -242,27 +295,30 @@ func (m editModel) inputFocusChange(msg tea.Msg) (editModel, tea.Cmd) {
inputHeight := 0

if len(m.inputs) > 0 {
inputHeight = lipgloss.Height(m.inputsView()) / len(m.inputs)
}

// Update index of the focused element
if key.Matches(keyMsg, m.keyMap.Up) && m.focusIndex > minFocusIndex { //nolint:gocritic // no need switch block here
m.focusIndex--

// Control viewport manually because height of input element is greater than one
// therefore, we need to scroll several lines at once instead of just a single line.
// Normally we don't need to handle scroll events, other than forward app messages to
// the viewport: m.viewport, cmd = m.viewport.Update(msg)
inputHeight = lipgloss.Height(m.inputsView()) / len(m.inputs)
}

Check warning on line 303 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L302-L303

Added lines #L302 - L303 were not covered by tests

// Update index of the focused element
if key.Matches(keyMsg, m.keyMap.Up) && m.focusedInput > minFocusIndex { //nolint:gocritic // no need switch block here
m.focusedInput--

Check warning on line 307 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L306-L307

Added lines #L306 - L307 were not covered by tests
m.viewport.LineUp(inputHeight)
} else if key.Matches(keyMsg, m.keyMap.Down) && m.focusIndex < maxFocusIndex {
m.focusIndex++
} else if key.Matches(keyMsg, m.keyMap.Down) && m.focusedInput < maxFocusIndex {
m.focusedInput++

Check warning on line 310 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L309-L310

Added lines #L309 - L310 were not covered by tests
m.viewport.LineDown(inputHeight)
} else {
return m, nil
}

for i := 0; i <= len(m.inputs)-1; i++ {
if i == m.focusIndex {
if m.inputs[i].Validate != nil {
m.inputs[i].Err = m.inputs[i].Validate(m.inputs[i].Value())
}

Check warning on line 319 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L317-L319

Added lines #L317 - L319 were not covered by tests

if i == m.focusedInput {

Check warning on line 321 in internal/ui/component/edithost/edit_host.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/edit_host.go#L321

Added line #L321 was not covered by tests
// Set focused state
cmd = m.inputs[i].Focus()
} else {
Expand Down
23 changes: 16 additions & 7 deletions internal/ui/component/edithost/label_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func NewLabelInput() labeledInput {
InputStyle: noStyle,
FocusedLabelStyle: focusedStyle,
FocusedInputStyle: focusedStyle,
ErrorStyle: errorStyle,

Check warning on line 25 in internal/ui/component/edithost/label_input.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/label_input.go#L25

Added line #L25 was not covered by tests
}
}

Expand All @@ -32,6 +33,7 @@ type labeledInput struct {
InputStyle lipgloss.Style
FocusedLabelStyle lipgloss.Style
FocusedInputStyle lipgloss.Style
ErrorStyle lipgloss.Style
FocusedPrompt string
Err error
}
Expand All @@ -40,7 +42,10 @@ func (l labeledInput) Update(msg tea.Msg) (labeledInput, tea.Cmd) {
var cmd tea.Cmd

l.Model, cmd = l.Model.Update(msg)
l.Err = l.Model.Err

if l.Model.Validate != nil {
l.Err = l.Model.Validate(l.Model.Value())
}

Check warning on line 48 in internal/ui/component/edithost/label_input.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/label_input.go#L46-L48

Added lines #L46 - L48 were not covered by tests

return l, cmd
}
Expand All @@ -54,7 +59,9 @@ func (l labeledInput) prompt() string {
}

func (l labeledInput) labelView() string {
if l.Focused() {
if l.Err != nil {
return l.prompt() + l.ErrorStyle.Render(l.Label)
} else if l.Focused() {

Check warning on line 64 in internal/ui/component/edithost/label_input.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/label_input.go#L62-L64

Added lines #L62 - L64 were not covered by tests
return l.prompt() + l.FocusedLabelStyle.Render(l.Label)
}

Expand All @@ -64,12 +71,14 @@ func (l labeledInput) labelView() string {
func (l labeledInput) View() string {
var view string
if l.Focused() {
if l.Err != nil {
view = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF7783")).Render(l.Model.Err.Error())
} else {
view = lipgloss.NewStyle().Foreground(lipgloss.Color("#AD58B4")).Render(l.Model.View())
}
view = lipgloss.NewStyle().Foreground(lipgloss.Color("#AD58B4")).Render(l.Model.View())
} else {
// if l.Err != nil {
// view = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF7783")).Render(l.Err.Error())
// } else {
// view = l.Model.View()
// }

Check warning on line 81 in internal/ui/component/edithost/label_input.go

View check run for this annotation

Codecov / codecov/patch

internal/ui/component/edithost/label_input.go#L76-L81

Added lines #L76 - L81 were not covered by tests
view = l.Model.View()
}

Expand Down
3 changes: 3 additions & 0 deletions internal/ui/component/edithost/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ var (
focusedStyle = lipgloss.NewStyle().
BorderForeground(lipgloss.AdaptiveColor{Light: "#F793FF", Dark: "#AD58B4"}).
Foreground(lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"})
errorStyle = lipgloss.NewStyle().
BorderForeground(lipgloss.AdaptiveColor{Light: "#F793FF", Dark: "#AD58B4"}).
Foreground(lipgloss.AdaptiveColor{Light: "#FF7783", Dark: "#FF7783"})
cursorStyle = focusedStyle.Copy()
noStyle = lipgloss.NewStyle()

Expand Down

0 comments on commit 7803c4f

Please sign in to comment.