Skip to content

Commit c261488

Browse files
authored
Merge pull request #7 from mabd-dev/dev
Dev
2 parents b4913b2 + 0283c82 commit c261488

30 files changed

Lines changed: 1062 additions & 738 deletions

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ It outputs results in both **human-friendly tables** and **machine-friendly JSON
1212

1313
🖼 Demo
1414

15-
https://github.com/user-attachments/assets/ba7fbb95-6566-41eb-bc1c-d67fb5adab5e
15+
16+
17+
https://github.com/user-attachments/assets/1c8370c6-3b94-4490-bc96-fc179ef14f1d
18+
19+
1620

1721

1822
---
@@ -122,7 +126,7 @@ Each step overrides the one before it
122126
- [x] Read user customizable `config.toml` file
123127
- [x] Export Report to json file
124128
- [x] Support dirignore
125-
- [ ] Worker pool for speed
129+
- [x] Worker pool for speed
126130
- [ ] Support git worktrees
127131
- [ ] Perform git push/pull/fetch on repos
128132
- [ ] Show branches with their states on each repo

cmd/reposcan/rootCmd.go

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ import (
55
"fmt"
66
"os"
77
"strings"
8-
"time"
98

9+
"github.com/mabd-dev/reposcan/internal"
1010
"github.com/mabd-dev/reposcan/internal/config"
11-
"github.com/mabd-dev/reposcan/internal/gitx"
1211
"github.com/mabd-dev/reposcan/internal/logger"
1312
"github.com/mabd-dev/reposcan/internal/render/file"
1413
"github.com/mabd-dev/reposcan/internal/render/stdout"
1514
"github.com/mabd-dev/reposcan/internal/render/tui"
16-
"github.com/mabd-dev/reposcan/internal/scan"
17-
"github.com/mabd-dev/reposcan/pkg/report"
1815
"github.com/spf13/cobra"
1916
)
2017

@@ -145,31 +142,7 @@ func readFlags(cmd *cobra.Command, configs *config.Config) error {
145142
}
146143

147144
func run(configs config.Config) error {
148-
reportWarnings := []string{}
149-
150-
// Find git repos at defined configs.Roots
151-
gitReposPaths, warnings := scan.FindGitRepos(configs.Roots, configs.DirIgnore)
152-
153-
reportWarnings = append(reportWarnings, warnings...)
154-
155-
repoStates := make([]report.RepoState, 0, len(gitReposPaths))
156-
157-
allRepoStates, warnings := gitx.GetGitRepoStatesConcurrent(gitReposPaths, configs.MaxWorkers)
158-
reportWarnings = append(reportWarnings, warnings...)
159-
160-
// filter repo states based on config OnlyFilter
161-
for _, repoState := range allRepoStates {
162-
if filter(configs.Only, repoState) {
163-
repoStates = append(repoStates, repoState)
164-
}
165-
}
166-
167-
report := report.ScanReport{
168-
Version: configs.Version,
169-
GeneratedAt: time.Now(),
170-
RepoStates: repoStates,
171-
Warnings: reportWarnings,
172-
}
145+
report := internal.GenerateScanReport(configs)
173146

174147
switch configs.Output.Type {
175148
case config.OutputJson:
@@ -180,7 +153,7 @@ func run(configs config.Config) error {
180153
case config.OutputTable:
181154
stdout.RenderScanReportAsTable(report)
182155
case config.OutputInteractive:
183-
if err := tui.ShowReportTUI(report, configs.Output.ColorSchemeName); err != nil {
156+
if err := tui.Render(report, configs); err != nil {
184157
fmt.Fprintf(os.Stderr, "tui error: %v\n", err)
185158
os.Exit(1)
186159
}
@@ -204,30 +177,3 @@ func run(configs config.Config) error {
204177

205178
return nil
206179
}
207-
208-
// Filter repoState based on config only filter
209-
// Returns true if repoState should be in output, false otherwise
210-
func filter(f config.OnlyFilter, repoState report.RepoState) bool {
211-
switch f {
212-
case config.OnlyAll:
213-
return true
214-
case config.OnlyDirty:
215-
if repoState.IsDirty() {
216-
return true
217-
}
218-
case config.OnlyUncommitted:
219-
if len(repoState.UncommitedFiles) > 0 {
220-
return true
221-
}
222-
case config.OnlyUnpushed:
223-
if repoState.Ahead > 0 {
224-
return true
225-
}
226-
case config.OnlyUnpulled:
227-
if repoState.Behind > 0 {
228-
return true
229-
}
230-
}
231-
232-
return false
233-
}

cmd/reposcan/versionCmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package reposcan
33
import (
44
"fmt"
55

6+
"github.com/mabd-dev/reposcan/internal"
67
"github.com/spf13/cobra"
78
)
89

910
var versionCmd = &cobra.Command{
1011
Use: "version",
1112
Short: "Print the version number of reposcan",
1213
Run: func(cmd *cobra.Command, args []string) {
13-
fmt.Printf("reposcan 1.3.5\n")
14+
fmt.Printf("reposcan " + internal.VERSION + "\n")
1415
},
1516
}

docs/release-notes/v1.3.6.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# v1.3.6 - UI Polish & Architecture Improvements
2+
3+
This release focuses on making the interactive TUI more intuitive and responsive, while significantly improving code organization for better maintainability.
4+
5+
## ✨ New Features
6+
7+
### Refresh on Demand
8+
- **New `r` keybinding**: Rescan your filesystem for repositories without restarting the app
9+
- Perfect for when you've just cloned new repos or want to refresh the current state
10+
11+
## 🎨 UI/UX Improvements
12+
13+
### Automatic Repository Details
14+
- Repository details now appear **automatically** when you select a repo - no more pressing Enter to toggle visibility
15+
- Shows maximum possible lines of changed files to give you the full picture at a glance
16+
17+
### Smarter Layout
18+
- **Repositioned filter input**: Moved to the footer when active
19+
- **Maximized table width**: Repository table now expands to fill the full terminal width
20+
- **Loop scrolling**: Navigate seamlessly - scrolling past the last item wraps to the first (and vice versa)
21+
22+
## 🏗️ Technical & Architecture Changes
23+
24+
### Focus Management System
25+
- Introduced **focus model stack** pattern for managing multiple interactive components
26+
- Cleaner state transitions between table navigation, text input, and popup overlays
27+
- Foundation for future interactive features
28+
29+
### Package Restructuring
30+
- **Dedicated TUI overlay package**: Centralized popup and overlay management
31+
- **Standalone repository details package**: Self-contained component that renders based on provided data
32+
- **Consistent TUI package structure**: Every custom Bubble Tea model now follows a standard layout:
33+
- `main.go` - initialization and exports
34+
- `types.go` - model definitions
35+
- `update.go` - message handling
36+
- `view.go` - rendering logic
37+
- Removed unused properties from main model for a cleaner codebase
38+
39+
### Benefits
40+
These architectural improvements make the codebase more maintainable, testable, and easier for contributors to navigate.
41+
42+
## 🐛 Bug Fixes
43+
44+
- **Filter cursor preservation**: Fixed issue where cursor position was lost when filtering repositories - now maintains your selection when possible

internal/keys.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package internal
2+
3+
var VERSION string = "v1.3.6"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package common
2+
3+
type Keybinding struct {
4+
Key string
5+
Description string
6+
ShortDesc string
7+
}

internal/render/tui/defaultUpdate.go

Lines changed: 0 additions & 69 deletions
This file was deleted.

internal/render/tui/focusModel.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package tui
2+
3+
import (
4+
"github.com/mabd-dev/reposcan/internal/render/tui/common"
5+
)
6+
7+
type FocusState int
8+
9+
const (
10+
FocusReposTable FocusState = iota
11+
FocusReposFilter
12+
FocusHelpPopup
13+
)
14+
15+
func (m Model) currentFocus() FocusState {
16+
if len(m.focusStack) == 0 {
17+
return FocusReposTable
18+
}
19+
return m.focusStack[len(m.focusStack)-1]
20+
}
21+
22+
func (m *Model) pushFocus(state FocusState) {
23+
m.blurCurrentModel()
24+
m.focusStack = append(m.focusStack, state)
25+
m.focusCurrentModel()
26+
}
27+
28+
func (m *Model) popFocus(reset bool) Model {
29+
m.blurCurrentModel()
30+
if reset {
31+
m.resetCurrentModel()
32+
}
33+
34+
if len(m.focusStack) > 1 {
35+
m.focusStack = m.focusStack[:len(m.focusStack)-1]
36+
}
37+
38+
m.focusCurrentModel()
39+
if reset {
40+
m.resetCurrentModel()
41+
}
42+
43+
return *m
44+
}
45+
46+
func (m *Model) focusCurrentModel() {
47+
switch m.currentFocus() {
48+
case FocusReposTable:
49+
m.reposTable.Focus()
50+
case FocusReposFilter:
51+
m.reposFilter.Focus()
52+
case FocusHelpPopup:
53+
break
54+
}
55+
}
56+
57+
func (m *Model) blurCurrentModel() {
58+
switch m.currentFocus() {
59+
case FocusReposTable:
60+
m.reposTable.Blur()
61+
case FocusReposFilter:
62+
m.reposFilter.Blur()
63+
case FocusHelpPopup:
64+
break
65+
}
66+
}
67+
68+
func (m *Model) resetCurrentModel() {
69+
switch m.currentFocus() {
70+
case FocusReposTable:
71+
m.reposTable.Filter("")
72+
case FocusReposFilter:
73+
m.reposFilter.SetValue("")
74+
case FocusHelpPopup:
75+
break
76+
}
77+
}
78+
79+
func (m *Model) keybindings() []common.Keybinding {
80+
switch m.currentFocus() {
81+
case FocusReposTable:
82+
return reposTableKeybindings
83+
case FocusReposFilter:
84+
return reposTableFilterKeybindings
85+
case FocusHelpPopup:
86+
return helpPopupKeybindings
87+
}
88+
return []common.Keybinding{}
89+
}

0 commit comments

Comments
 (0)