Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions internal/tui/board_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
type filterTextClickedMsg struct{}
type filterLabelClickedMsg struct{ tag string }
type filterClearClickedMsg struct{}
type boardPointerDownMsg struct{ taskID string }
type boardPointerMoveMsg struct {
status board.Status
beforeTaskID string
}
type boardPointerUpMsg struct{}

type boardHitKind uint8

Expand Down Expand Up @@ -237,7 +243,7 @@
return 0
}

func (m Model) renderBoard() (string, []boardHit) {

Check failure on line 246 in internal/tui/board_view.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_kb&issues=AaAQgt_v67TddhEavXtP&open=AaAQgt_v67TddhEavXtP&pullRequest=104
width := max(m.width, 1)
height := max(m.height, 8)
title := strings.TrimSpace(m.board.Title)
Expand Down Expand Up @@ -265,15 +271,21 @@
hits = append(filterHits, hits...)

state := "ready"
if m.loading || (m.watcher != nil && !m.haveVersion) {
state = "loading board..."
}
if m.loadErr != nil {
moveActive := m.move.lifted != nil || m.move.saving
movePriority := moveActive || m.move.notice
showMoveStatus := movePriority && m.move.status != ""
if showMoveStatus {
state = sanitizeTerminal(m.move.status)
} else if m.loadErr != nil {
state = "error: " + m.loadErr.Error()
} else if m.pollErr != nil {
state = "error: " + m.pollErr.Error()
} else if m.preferenceErr != nil {
state = "error: " + m.preferenceErr.Error()
} else if m.move.status != "" {
state = sanitizeTerminal(m.move.status)
} else if m.loading || (m.watcher != nil && !m.haveVersion) {
state = "loading board..."
}
cancelled := "off"
if m.boardView.showCancelled {
Expand All @@ -283,6 +295,10 @@
if m.editor.Enabled() {
help = "n new | e edit | " + help
}
if showMoveStatus || (m.move.status != "" && m.loadErr == nil && m.pollErr == nil && m.preferenceErr == nil) {
footer := fitLine(state, width)
return strings.Join([]string{header, filterLine, body, footer}, "\n"), hits
}
if m.settingsNew != nil {
footer := settingsBoardFooter(state, cancelled, m.editor.Enabled(), width)
return strings.Join([]string{header, filterLine, body, footer}, "\n"), hits
Expand Down Expand Up @@ -368,7 +384,7 @@

func settingsBoardFooter(state, cancelled string, editorEnabled bool, width int) string {
candidates := [][]string{
{"s settings", "j/k cards", "h/l/tab columns", "1-4 jump", "c cancelled:" + cancelled, "q quit"},

Check failure on line 387 in internal/tui/board_view.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "s settings" 9 times.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_kb&issues=AaAQgt_v67TddhEavXtL&open=AaAQgt_v67TddhEavXtL&pullRequest=104
{"s settings", "j/k cards", "h/l/tab columns", "c cancelled:" + cancelled, "q quit"},
{"s settings", "j/k cards", "h/l/tab columns", "q quit"},
{"s settings", "j/k", "h/l/tab", "q quit"},
Expand All @@ -380,7 +396,7 @@
}
if editorEnabled {
candidates = append([][]string{
{"s settings", "n new", "e edit", "j/k cards", "h/l/tab columns", "1-4 jump", "c cancelled:" + cancelled, "q quit"},

Check failure on line 399 in internal/tui/board_view.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "e edit" 4 times.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_kb&issues=AaASce0rROXJ_dC3dB4T&open=AaASce0rROXJ_dC3dB4T&pullRequest=104
{"s settings", "n new", "e edit", "j/k cards", "h/l/tab columns", "c cancelled:" + cancelled, "q quit"},
{"s settings", "n new", "e edit", "j/k cards", "c cancelled:" + cancelled, "q quit"},
{"s settings", "n new", "e edit", "j/k cards", "h/l/tab columns", "q quit"},
Expand Down Expand Up @@ -476,6 +492,9 @@
if m.boardView.column == statusIndex(status) && i == selected {
marker = "› "
}
if m.move.lifted != nil && task.ID == m.move.lifted.taskID {
marker = "↕ "
}
first := cardHeading(task, m.now())
for lineIndex, line := range wrapTokens(first, max(width-2, 1)) {
prefix := " "
Expand Down Expand Up @@ -558,30 +577,56 @@
return strings.Join(lines, "\n"), hits
}

func boardMouseHandler(hits []boardHit) func(tea.MouseMsg) tea.Cmd {
func boardMouseHandler(hits []boardHit, active ...bool) func(tea.MouseMsg) tea.Cmd {

Check failure on line 580 in internal/tui/board_view.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 37 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_kb&issues=AaASce0rROXJ_dC3dB4U&open=AaASce0rROXJ_dC3dB4U&pullRequest=104
pointerActive := len(active) > 0 && active[0]
return func(message tea.MouseMsg) tea.Cmd {
click, ok := message.(tea.MouseClickMsg)
if !ok || click.Button != tea.MouseLeft {
mouse := message.Mouse()
if _, release := message.(tea.MouseReleaseMsg); release {
if mouse.Button == tea.MouseLeft || (mouse.Button == tea.MouseNone && pointerActive) {
return func() tea.Msg { return boardPointerUpMsg{} }
}
return nil
}
mouse := click.Mouse()
if mouse.Button != tea.MouseLeft {
return nil
}
var matched *boardHit
var dragAnchor *boardHit
for i := len(hits) - 1; i >= 0; i-- {
hit := hits[i]
if mouse.X < hit.x0 || mouse.X >= hit.x1 || mouse.Y < hit.y0 || mouse.Y >= hit.y1 {
continue
}
switch hit.kind {
if matched == nil {
matched = &hit
}
if dragAnchor == nil && hit.kind == boardHitDefault {
dragAnchor = &hit
}
}
switch message.(type) {
case tea.MouseClickMsg:
if matched == nil {
return nil
}
switch matched.kind {
case boardHitFilterText:
return func() tea.Msg { return filterTextClickedMsg{} }
case boardHitFilterLabel:
return func() tea.Msg { return filterLabelClickedMsg{tag: hit.tag} }
return func() tea.Msg { return filterLabelClickedMsg{tag: matched.tag} }
case boardHitFilterClear:
return func() tea.Msg { return filterClearClickedMsg{} }
}
if hit.taskID != "" {
return func() tea.Msg { return boardCardClickedMsg{taskID: hit.taskID} }
if matched.taskID != "" {
return func() tea.Msg { return boardPointerDownMsg{taskID: matched.taskID} }
}
return func() tea.Msg { return boardColumnClickedMsg{status: matched.status} }
case tea.MouseMotionMsg:
if dragAnchor != nil {
return func() tea.Msg {
return boardPointerMoveMsg{status: dragAnchor.status, beforeTaskID: dragAnchor.taskID}
}
}
return func() tea.Msg { return boardColumnClickedMsg{status: hit.status} }
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions internal/tui/board_view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,18 @@
break
}
}
command := boardMouseHandler(hits)(tea.MouseClickMsg{X: cardHit.x0 + 1, Y: cardHit.y0, Button: tea.MouseLeft})
command := boardMouseHandler(hits, false)(tea.MouseClickMsg{X: cardHit.x0 + 1, Y: cardHit.y0, Button: tea.MouseLeft})
if command == nil {
t.Fatal("card click was not hit")
}
updateTestModel(t, &m, command())
if selected, ok := m.selectedTask(); !ok || selected.ID != "doing-1" {
t.Fatalf("mouse selection = %+v,%v", selected, ok)
}
if command := boardMouseHandler(hits)(tea.MouseReleaseMsg{}); command != nil {
if command := boardMouseHandler(hits, false)(tea.MouseReleaseMsg{}); command != nil {
t.Fatalf("release produced command %v", command)
}
if command := boardMouseHandler(hits)(tea.MouseClickMsg{X: 999, Y: 999, Button: tea.MouseLeft}); command != nil {
if command := boardMouseHandler(hits, false)(tea.MouseClickMsg{X: 999, Y: 999, Button: tea.MouseLeft}); command != nil {
t.Fatalf("off-board click produced command %v", command)
}

Expand Down Expand Up @@ -518,7 +518,7 @@
return err
}

func TestCancelledPreferenceAtomicFailuresPreservePriorFile(t *testing.T) {

Check failure on line 521 in internal/tui/board_view_test.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 21 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=RandomCodeSpace_kb&issues=AaAQgt8_67TddhEavXtF&open=AaAQgt8_67TddhEavXtF&pullRequest=104
failure := errors.New("injected preference failure")
for _, stage := range []string{"write", "short write", "sync", "close", "rename"} {
t.Run(stage, func(t *testing.T) {
Expand Down
Loading