Function bodies 54 total
main function · go · L13-L28 (16 LOC)cmd/gopener/main.go
func main() {
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "gopener: failed to load config: %v\n", err)
os.Exit(1)
}
l := launcher.New()
app := tui.NewApp(cfg, l)
p := tea.NewProgram(app, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "gopener: %v\n", err)
os.Exit(1)
}
}configPath function · go · L29-L39 (11 LOC)internal/config/config.go
func configPath() (string, error) {
base := os.Getenv("XDG_CONFIG_HOME")
if base == "" {
var err error
base, err = os.UserConfigDir()
if err != nil {
return "", err
}
}
return filepath.Join(base, "gopener", "config.json"), nil
}Load function · go · L41-L60 (20 LOC)internal/config/config.go
func Load() (*Config, error) {
path, err := configPath()
if err != nil {
return nil, err
}
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
return Default(), nil
}
if err != nil {
return nil, err
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}Save method · go · L62-L77 (16 LOC)internal/config/config.go
func (c *Config) Save() error {
path, err := configPath()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}FindDir method · go · L80-L87 (8 LOC)internal/config/config.go
func (c *Config) FindDir(path string) *DirConfig {
for i := range c.Directories {
if c.Directories[i].Path == path {
return &c.Directories[i]
}
}
return nil
}FindProfile method · go · L90-L97 (8 LOC)internal/config/config.go
func (c *Config) FindProfile(id string) *Profile {
for i := range c.Profiles {
if c.Profiles[i].ID == id {
return &c.Profiles[i]
}
}
return nil
}Default function · go · L11-L22 (12 LOC)internal/config/defaults.go
func Default() *Config {
return &Config{
SrcDir: defaultSrcDir(),
Terminal: DetectTerminal(),
Profiles: []Profile{
{ID: newID(), Label: "Claude", Cmd: "claude --continue"},
{ID: newID(), Label: "Claude YOLO", Cmd: "claude --continue --dangerously-skip-permissions"},
{ID: newID(), Label: "VS Code", Cmd: "code ."},
{ID: newID(), Label: "IntelliJ", Cmd: "idea ."},
},
}
}Repobility · severity-and-effort ranking · https://repobility.com
defaultSrcDir function · go · L24-L30 (7 LOC)internal/config/defaults.go
func defaultSrcDir() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, "src")
}newID function · go · L32-L36 (5 LOC)internal/config/defaults.go
func newID() string {
b := make([]byte, 8)
_, _ = rand.Read(b)
return fmt.Sprintf("%x", b)
}DetectTerminal function · go · L13-L41 (29 LOC)internal/config/terminal_darwin.go
func DetectTerminal() string {
// First, try to detect the current terminal from the parent process
if current := detectCurrentTerminal(); current != "" {
return current
}
// List of terminal emulators to check, in order of preference
terminals := []struct {
name string
path string
}{
{"Ghostty", "/Applications/Ghostty.app"},
{"iTerm", "/Applications/iTerm.app"},
{"Warp", "/Applications/Warp.app"},
{"Kitty", "/Applications/kitty.app"},
{"Alacritty", "/Applications/Alacritty.app"},
{"Hyper", "/Applications/Hyper.app"},
{"Terminal", "/System/Applications/Utilities/Terminal.app"},
}
for _, term := range terminals {
if _, err := os.Stat(term.path); err == nil {
return term.name
}
}
// Fallback to default Terminal.app (always available on macOS)
return "Terminal"
}detectCurrentTerminal function · go · L44-L77 (34 LOC)internal/config/terminal_darwin.go
func detectCurrentTerminal() string {
// Check TERM_PROGRAM environment variable (set by many modern terminals)
if termProgram := os.Getenv("TERM_PROGRAM"); termProgram != "" {
switch termProgram {
case "iTerm.app":
return "iTerm"
case "Apple_Terminal":
return "Terminal"
case "WarpTerminal":
return "Warp"
case "Hyper":
return "Hyper"
case "ghostty":
return "Ghostty"
}
}
// Check for Ghostty-specific environment variable
if os.Getenv("GHOSTTY_RESOURCES_DIR") != "" {
return "Ghostty"
}
// Check for Kitty-specific environment variable
if os.Getenv("KITTY_WINDOW_ID") != "" {
return "Kitty"
}
// Check for Alacritty-specific environment variable
if os.Getenv("ALACRITTY_SOCKET") != "" || os.Getenv("ALACRITTY_LOG") != "" {
return "Alacritty"
}
return ""
}AvailableTerminals function · go · L80-L111 (32 LOC)internal/config/terminal_darwin.go
func AvailableTerminals() []string {
terminals := []struct {
name string
path string
}{
{"Terminal", "/System/Applications/Utilities/Terminal.app"},
{"Ghostty", "/Applications/Ghostty.app"},
{"iTerm", "/Applications/iTerm.app"},
{"Warp", "/Applications/Warp.app"},
{"Kitty", "/Applications/kitty.app"},
{"Alacritty", "/Applications/Alacritty.app"},
{"Hyper", "/Applications/Hyper.app"},
}
var available []string
for _, term := range terminals {
if _, err := os.Stat(term.path); err == nil {
available = append(available, term.name)
}
// Also check in user's Applications folder
userPath := filepath.Join(os.Getenv("HOME"), "Applications", filepath.Base(term.path))
if _, err := os.Stat(userPath); err == nil && !contains(available, term.name) {
available = append(available, term.name)
}
}
if len(available) == 0 {
available = append(available, "Terminal")
}
return available
}contains function · go · L113-L120 (8 LOC)internal/config/terminal_darwin.go
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}DetectTerminal function · go · L13-L58 (46 LOC)internal/config/terminal_linux.go
func DetectTerminal() string {
// First, check TERMINAL environment variable (often set by users)
if term := os.Getenv("TERMINAL"); term != "" {
if _, err := exec.LookPath(term); err == nil {
return term
}
}
// Try to detect from TERM_PROGRAM (set by some modern terminals)
if termProgram := os.Getenv("TERM_PROGRAM"); termProgram != "" {
if _, err := exec.LookPath(termProgram); err == nil {
return termProgram
}
}
// Check for terminal-specific environment variables
if os.Getenv("KITTY_WINDOW_ID") != "" {
return "kitty"
}
if os.Getenv("ALACRITTY_SOCKET") != "" || os.Getenv("ALACRITTY_LOG") != "" {
return "alacritty"
}
if os.Getenv("WARP_USE_SSH_WRAPPER") != "" {
return "warp-terminal"
}
// List of terminal emulators to check, in order of preference
terminals := []string{
"ghostty",
"alacritty",
"kitty",
"warp-terminal",
"gnome-terminal",
"konsole",
"xterm",
}
for _, term := range terminals {
if _, err := exec.LookPath(term); err ==AvailableTerminals function · go · L61-L88 (28 LOC)internal/config/terminal_linux.go
func AvailableTerminals() []string {
terminals := []string{
"ghostty",
"alacritty",
"kitty",
"warp-terminal",
"gnome-terminal",
"konsole",
"xfce4-terminal",
"mate-terminal",
"xterm",
"urxvt",
"terminator",
}
var available []string
for _, term := range terminals {
if _, err := exec.LookPath(term); err == nil {
available = append(available, term)
}
}
if len(available) == 0 {
available = append(available, "xterm")
}
return available
}Repobility · code-quality intelligence platform · https://repobility.com
New function · go · L15-L17 (3 LOC)internal/launcher/launcher_darwin.go
func New() Launcher {
return &darwinLauncher{}
}escapeAppleScript function · go · L20-L25 (6 LOC)internal/launcher/launcher_darwin.go
func escapeAppleScript(s string) string {
// Escape backslashes first, then double quotes
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return s
}Launch method · go · L27-L101 (75 LOC)internal/launcher/launcher_darwin.go
func (l *darwinLauncher) Launch(dirs []config.DirConfig, profiles []config.Profile, terminal string) error {
// Default to Terminal.app if not specified
if terminal == "" {
terminal = "Terminal"
}
// Build profile map for quick lookup.
profileMap := make(map[string]config.Profile, len(profiles))
for _, p := range profiles {
profileMap[p.ID] = p
}
for _, dir := range dirs {
if !dir.Enabled {
continue
}
for _, pid := range dir.ProfileIDs {
p, ok := profileMap[pid]
if !ok {
continue
}
// Escape the path and command for AppleScript
escapedPath := escapeAppleScript(dir.Path)
escapedCmd := escapeAppleScript(p.Cmd)
var cmd *exec.Cmd
switch terminal {
case "Ghostty":
// Ghostty on macOS needs to use 'open' command with -e flag
// Use -a instead of -na to open in existing instance
shellCmd := fmt.Sprintf("cd \"%s\" && %s", escapedPath, escapedCmd)
cmd = exec.Command("open", "-a", "Ghostty.app", "--args", "-e", "sh", "-c", shNew function · go · L15-L17 (3 LOC)internal/launcher/launcher_linux.go
func New() Launcher {
return &linuxLauncher{}
}Launch method · go · L19-L54 (36 LOC)internal/launcher/launcher_linux.go
func (l *linuxLauncher) Launch(dirs []config.DirConfig, profiles []config.Profile, terminal string) error {
term := terminal
if term == "" {
term = detectTerminal()
}
if term == "" {
return fmt.Errorf("no supported terminal emulator found")
}
// Build profile map for quick lookup.
profileMap := make(map[string]config.Profile, len(profiles))
for _, p := range profiles {
profileMap[p.ID] = p
}
for _, dir := range dirs {
if !dir.Enabled {
continue
}
for _, pid := range dir.ProfileIDs {
p, ok := profileMap[pid]
if !ok {
continue
}
shellCmd := fmt.Sprintf("cd %q && %s", dir.Path, p.Cmd)
cmd := buildCmd(term, shellCmd)
if cmd == nil {
continue
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("launching %s for %s: %w", p.Label, dir.Name, err)
}
}
}
return nil
}detectTerminal function · go · L56-L69 (14 LOC)internal/launcher/launcher_linux.go
func detectTerminal() string {
if t := os.Getenv("TERMINAL"); t != "" {
if path, err := exec.LookPath(t); err == nil && path != "" {
return t
}
}
candidates := []string{"ghostty", "wezterm", "kitty", "alacritty", "konsole", "gnome-terminal", "xterm"}
for _, c := range candidates {
if _, err := exec.LookPath(c); err == nil {
return c
}
}
return ""
}buildCmd function · go · L71-L90 (20 LOC)internal/launcher/launcher_linux.go
func buildCmd(term, shellCmd string) *exec.Cmd {
switch term {
case "ghostty":
return exec.Command("ghostty", "-e", "bash", "-c", shellCmd)
case "wezterm":
return exec.Command("wezterm", "start", "--", "bash", "-c", shellCmd)
case "kitty":
return exec.Command("kitty", "bash", "-c", shellCmd)
case "alacritty":
return exec.Command("alacritty", "-e", "bash", "-c", shellCmd)
case "konsole":
return exec.Command("konsole", "-e", "bash", "-c", shellCmd)
case "gnome-terminal":
return exec.Command("gnome-terminal", "--", "bash", "-c", shellCmd)
case "xterm":
return exec.Command("xterm", "-e", "bash", "-c", shellCmd)
default:
return exec.Command(term, "-e", "bash", "-c", shellCmd)
}
}Scan function · go · L14-L49 (36 LOC)internal/scanner/scanner.go
func Scan(srcDir string, existing []config.DirConfig) ([]config.DirConfig, error) {
entries, err := os.ReadDir(srcDir)
if err != nil {
return nil, err
}
// Index existing configs by path for quick lookup.
byPath := make(map[string]config.DirConfig, len(existing))
for _, d := range existing {
byPath[d.Path] = d
}
var result []config.DirConfig
for _, e := range entries {
if !e.IsDir() {
continue
}
fullPath := filepath.Join(srcDir, e.Name())
if d, ok := byPath[fullPath]; ok {
result = append(result, d)
} else {
result = append(result, config.DirConfig{
Path: fullPath,
Name: e.Name(),
Enabled: false,
ProfileIDs: nil,
})
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name < result[j].Name
})
return result, nil
}All rows scored by the Repobility analyzer (https://repobility.com)
NewApp function · go · L33-L52 (20 LOC)internal/tui/app.go
func NewApp(cfg *config.Config, l launcher.Launcher) *App {
app := &App{cfg: cfg, launcher: l}
if cfg.SrcDir == "" {
app.screen = screenSetup
} else {
app.screen = screenMain
// Scan on startup.
if dirs, err := scanner.Scan(cfg.SrcDir, cfg.Directories); err == nil {
cfg.Directories = dirs
_ = cfg.Save()
}
}
app.setup = setup.New(cfg)
app.main = mainscreen.New(cfg, l)
app.profiles = profiles.New(cfg)
app.settings = settings.New(cfg)
return app
}Init method · go · L54-L66 (13 LOC)internal/tui/app.go
func (a *App) Init() tea.Cmd {
switch a.screen {
case screenSetup:
return a.setup.Init()
case screenMain:
return a.main.Init()
case screenProfiles:
return a.profiles.Init()
case screenSettings:
return a.settings.Init()
}
return nil
}Update method · go · L68-L125 (58 LOC)internal/tui/app.go
func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch a.screen {
case screenSetup:
updated, cmd := a.setup.Update(msg)
a.setup = updated
// Check if setup is done.
if _, ok := msg.(setup.DoneMsg); ok {
done := msg.(setup.DoneMsg)
a.cfg.SrcDir = done.SrcDir
if dirs, err := scanner.Scan(a.cfg.SrcDir, a.cfg.Directories); err == nil {
a.cfg.Directories = dirs
}
_ = a.cfg.Save()
a.main = mainscreen.New(a.cfg, a.launcher)
a.screen = screenMain
return a, a.main.Init()
}
return a, cmd
case screenMain:
updated, cmd := a.main.Update(msg)
a.main = updated
if _, ok := msg.(mainscreen.GoProfilesMsg); ok {
a.profiles = profiles.New(a.cfg)
a.screen = screenProfiles
return a, a.profiles.Init()
}
if _, ok := msg.(mainscreen.GoSettingsMsg); ok {
a.settings = settings.New(a.cfg)
a.screen = screenSettings
return a, a.settings.Init()
}
return a, cmd
case screenProfiles:
updated, cmd := a.profiles.Update(msg)
a.profiView method · go · L127-L139 (13 LOC)internal/tui/app.go
func (a *App) View() string {
switch a.screen {
case screenSetup:
return a.setup.View()
case screenMain:
return a.main.View()
case screenProfiles:
return a.profiles.View()
case screenSettings:
return a.settings.View()
}
return ""
}New function · go · L54-L65 (12 LOC)internal/tui/screens/main/main.go
func New(cfg *config.Config, l launcher.Launcher) Model {
ti := textinput.New()
ti.Placeholder = "/home/user/src"
ti.CharLimit = 256
ti.Width = 50
return Model{
cfg: cfg,
launcher: l,
srcInput: ti,
height: 24,
}
}Update method · go · L69-L83 (15 LOC)internal/tui/screens/main/main.go
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if wm, ok := msg.(tea.WindowSizeMsg); ok {
m.height = wm.Height
m.clampScroll()
}
switch m.mode {
case modeList:
return m.updateList(msg)
case modeAssign:
return m.updateAssign(msg)
case modeChangeSrc:
return m.updateChangeSrc(msg)
}
return m, nil
}updateList method · go · L85-L159 (75 LOC)internal/tui/screens/main/main.go
func (m Model) updateList(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Main.Quit):
return m, tea.Quit
case key.Matches(msg, keys.Main.Up):
if m.cursor > 0 {
m.cursor--
m.clampScroll()
}
case key.Matches(msg, keys.Main.Down):
if m.cursor < len(m.cfg.Directories)-1 {
m.cursor++
m.clampScroll()
}
case key.Matches(msg, keys.Main.PageUp):
m.cursor -= m.visibleRows()
if m.cursor < 0 {
m.cursor = 0
}
m.clampScroll()
case key.Matches(msg, keys.Main.PageDown):
m.cursor += m.visibleRows()
if m.cursor > len(m.cfg.Directories)-1 {
m.cursor = len(m.cfg.Directories) - 1
}
m.clampScroll()
case key.Matches(msg, keys.Main.Toggle):
if len(m.cfg.Directories) > 0 {
m.cfg.Directories[m.cursor].Enabled = !m.cfg.Directories[m.cursor].Enabled
_ = m.cfg.Save()
}
case key.Matches(msg, keys.Main.Assign):
if len(m.cfg.Directories) > 0 {
m.enterAssvisibleRows method · go · L162-L168 (7 LOC)internal/tui/screens/main/main.go
func (m Model) visibleRows() int {
rows := m.height - reservedLines
if rows < 3 {
rows = 3
}
return rows
}Open data scored by Repobility · https://repobility.com
clampScroll method · go · L171-L189 (19 LOC)internal/tui/screens/main/main.go
func (m *Model) clampScroll() {
visible := m.visibleRows()
if m.cursor < m.scrollOffset {
m.scrollOffset = m.cursor
}
if m.cursor >= m.scrollOffset+visible {
m.scrollOffset = m.cursor - visible + 1
}
maxOffset := len(m.cfg.Directories) - visible
if maxOffset < 0 {
maxOffset = 0
}
if m.scrollOffset > maxOffset {
m.scrollOffset = maxOffset
}
if m.scrollOffset < 0 {
m.scrollOffset = 0
}
}enterAssign method · go · L191-L201 (11 LOC)internal/tui/screens/main/main.go
func (m *Model) enterAssign(dirIdx int) {
m.mode = modeAssign
m.assignDirIdx = dirIdx
m.assignCursor = 0
// snapshot current profile selections for this dir
selected := make(map[string]bool)
for _, pid := range m.cfg.Directories[dirIdx].ProfileIDs {
selected[pid] = true
}
m.assignToggled = selected
}updateAssign method · go · L203-L236 (34 LOC)internal/tui/screens/main/main.go
func (m Model) updateAssign(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Assign.Back):
m.mode = modeList
case key.Matches(msg, keys.Assign.Up):
if m.assignCursor > 0 {
m.assignCursor--
}
case key.Matches(msg, keys.Assign.Down):
if m.assignCursor < len(m.cfg.Profiles)-1 {
m.assignCursor++
}
case key.Matches(msg, keys.Assign.Toggle):
if len(m.cfg.Profiles) > 0 {
pid := m.cfg.Profiles[m.assignCursor].ID
m.assignToggled[pid] = !m.assignToggled[pid]
}
case key.Matches(msg, keys.Assign.Confirm):
// Save selections back.
var ids []string
for _, p := range m.cfg.Profiles {
if m.assignToggled[p.ID] {
ids = append(ids, p.ID)
}
}
m.cfg.Directories[m.assignDirIdx].ProfileIDs = ids
_ = m.cfg.Save()
m.mode = modeList
}
}
return m, nil
}updateChangeSrc method · go · L238-L268 (31 LOC)internal/tui/screens/main/main.go
func (m Model) updateChangeSrc(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEsc:
m.srcInput.Blur()
m.mode = modeList
return m, nil
case tea.KeyEnter:
val := strings.TrimSpace(m.srcInput.Value())
if val == "" {
return m, nil
}
m.cfg.SrcDir = val
dirs, err := scanner.Scan(val, m.cfg.Directories)
if err != nil {
m.statusMsg = fmt.Sprintf("scan error: %v", err)
} else {
m.cfg.Directories = dirs
m.statusMsg = fmt.Sprintf("src changed, %d dirs", len(dirs))
}
_ = m.cfg.Save()
m.srcInput.Blur()
m.mode = modeList
return m, nil
}
}
var cmd tea.Cmd
m.srcInput, cmd = m.srcInput.Update(msg)
return m, cmd
}View method · go · L270-L279 (10 LOC)internal/tui/screens/main/main.go
func (m Model) View() string {
switch m.mode {
case modeAssign:
return m.viewAssign()
case modeChangeSrc:
return m.viewChangeSrc()
default:
return m.viewList()
}
}viewList method · go · L281-L337 (57 LOC)internal/tui/screens/main/main.go
func (m Model) viewList() string {
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render("gopener")
srcLine := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("src: " + m.cfg.SrcDir)
var sb strings.Builder
sb.WriteString(title + " " + srcLine + "\n\n")
if len(m.cfg.Directories) == 0 {
sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(" (no directories — press r to scan)") + "\n")
}
visible := m.visibleRows()
start := m.scrollOffset
end := start + visible
if end > len(m.cfg.Directories) {
end = len(m.cfg.Directories)
}
for i := start; i < end; i++ {
d := m.cfg.Directories[i]
check := "[ ]"
if d.Enabled {
check = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("[x]")
}
// Collect profile labels for this dir.
var labels []string
for _, pid := range d.ProfileIDs {
if p := m.cfg.FindProfile(pid); p != nil {
labels = append(labels, p.Label)
}
}
profilesStr := ""viewChangeSrc method · go · L339-L343 (5 LOC)internal/tui/screens/main/main.go
func (m Model) viewChangeSrc() string {
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render("Change source directory")
help := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(" enter confirm esc cancel")
return strings.Join([]string{title, "", " " + m.srcInput.View(), "", help}, "\n")
}viewAssign method · go · L345-L380 (36 LOC)internal/tui/screens/main/main.go
func (m Model) viewAssign() string {
if m.assignDirIdx >= len(m.cfg.Directories) {
return ""
}
dir := m.cfg.Directories[m.assignDirIdx]
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render(
fmt.Sprintf("Assign profiles → %s", dir.Name),
)
var sb strings.Builder
sb.WriteString(title + "\n\n")
if len(m.cfg.Profiles) == 0 {
sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(" (no profiles — press esc, then p to add)") + "\n")
}
for i, p := range m.cfg.Profiles {
check := "[ ]"
if m.assignToggled[p.ID] {
check = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("[x]")
}
cursor := " "
label := p.Label
if i == m.assignCursor {
cursor = "▸ "
label = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true).Render(p.Label)
}
sb.WriteString(fmt.Sprintf("%s%s %s %s\n", cursor, check, label, p.Cmd))
}
help := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(
"\n sRepobility · severity-and-effort ranking · https://repobility.com
New function · go · L41-L53 (13 LOC)internal/tui/screens/profiles/profiles.go
func New(cfg *config.Config) Model {
label := textinput.New()
label.Placeholder = "label"
label.CharLimit = 64
label.Width = 30
cmd := textinput.New()
cmd.Placeholder = "command"
cmd.CharLimit = 256
cmd.Width = 50
return Model{cfg: cfg, labelIn: label, cmdIn: cmd}
}Update method · go · L57-L65 (9 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch m.mode {
case modeList:
return m.updateList(msg)
case modeAdd, modeEdit:
return m.updateEdit(msg)
}
return m, nil
}updateList method · go · L67-L117 (51 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) updateList(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Profile.Back):
return m, func() tea.Msg { return BackMsg{} }
case key.Matches(msg, keys.Profile.Up):
if m.cursor > 0 {
m.cursor--
}
case key.Matches(msg, keys.Profile.Down):
if m.cursor < len(m.cfg.Profiles)-1 {
m.cursor++
}
case key.Matches(msg, keys.Profile.Add):
m.mode = modeAdd
m.labelIn.SetValue("")
m.cmdIn.SetValue("")
m.focused = 0
m.labelIn.Focus()
m.cmdIn.Blur()
m.err = ""
return m, textinput.Blink
case key.Matches(msg, keys.Profile.Edit):
if len(m.cfg.Profiles) == 0 {
return m, nil
}
m.mode = modeEdit
m.editIdx = m.cursor
p := m.cfg.Profiles[m.cursor]
m.labelIn.SetValue(p.Label)
m.cmdIn.SetValue(p.Cmd)
m.focused = 0
m.labelIn.Focus()
m.cmdIn.Blur()
m.err = ""
return m, textinput.Blink
case key.Matches(msg, keys.Profile.Delete):
if len(mupdateEdit method · go · L119-L166 (48 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) updateEdit(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEsc:
m.mode = modeList
return m, nil
case tea.KeyTab, tea.KeyShiftTab:
m.focused = 1 - m.focused
if m.focused == 0 {
m.labelIn.Focus()
m.cmdIn.Blur()
} else {
m.labelIn.Blur()
m.cmdIn.Focus()
}
return m, textinput.Blink
case tea.KeyEnter:
label := strings.TrimSpace(m.labelIn.Value())
cmd := strings.TrimSpace(m.cmdIn.Value())
if label == "" || cmd == "" {
m.err = "label and command are required"
return m, nil
}
if m.mode == modeAdd {
m.cfg.Profiles = append(m.cfg.Profiles, config.Profile{
ID: newID(),
Label: label,
Cmd: cmd,
})
} else {
m.cfg.Profiles[m.editIdx].Label = label
m.cfg.Profiles[m.editIdx].Cmd = cmd
}
m.mode = modeList
m.err = ""
_ = m.cfg.Save()
return m, func() tea.Msg { return SavedMsg{} }
}
}
var cmds []tea.Cmd
varView method · go · L168-L176 (9 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) View() string {
switch m.mode {
case modeList:
return m.viewList()
case modeAdd, modeEdit:
return m.viewEdit()
}
return ""
}viewList method · go · L178-L204 (27 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) viewList() string {
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render("Profiles")
var sb strings.Builder
sb.WriteString(title + "\n\n")
if len(m.cfg.Profiles) == 0 {
sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(" (no profiles)") + "\n")
}
for i, p := range m.cfg.Profiles {
cursor := " "
line := fmt.Sprintf("%s %-20s %s", cursor, p.Label, p.Cmd)
if i == m.cursor {
cursor = "▸ "
line = fmt.Sprintf("%s %-20s %s", cursor, p.Label, p.Cmd)
line = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true).Render(line)
} else {
line = lipgloss.NewStyle().Foreground(lipgloss.Color("252")).Render(line)
}
sb.WriteString(line + "\n")
}
help := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(
"\n a add e edit d delete esc back",
)
sb.WriteString(help)
return sb.String()
}viewEdit method · go · L206-L227 (22 LOC)internal/tui/screens/profiles/profiles.go
func (m Model) viewEdit() string {
heading := "Add Profile"
if m.mode == modeEdit {
heading = "Edit Profile"
}
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render(heading)
labelLabel := "Label:"
cmdLabel := "Command:"
if m.focused == 0 {
labelLabel = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Render("Label:")
} else {
cmdLabel = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Render("Command:")
}
parts := []string{title, "", labelLabel, " " + m.labelIn.View(), "", cmdLabel, " " + m.cmdIn.View()}
if m.err != "" {
parts = append(parts, "", lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render(" "+m.err))
}
parts = append(parts, "", lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(" tab switch field enter save esc cancel"))
return strings.Join(parts, "\n")
}newID function · go · L230-L234 (5 LOC)internal/tui/screens/profiles/profiles.go
func newID() string {
b := make([]byte, 8)
_, _ = rand.Read(b)
return fmt.Sprintf("%x", b)
}Repobility · code-quality intelligence platform · https://repobility.com
New function · go · L24-L29 (6 LOC)internal/tui/screens/settings/settings.go
func New(cfg *config.Config) Model {
return Model{
cfg: cfg,
availableTerms: config.AvailableTerminals(),
}
}Update method · go · L33-L59 (27 LOC)internal/tui/screens/settings/settings.go
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Settings.Back):
return m, func() tea.Msg { return GoBackMsg{} }
case key.Matches(msg, keys.Settings.Up):
if m.cursor > 0 {
m.cursor--
}
case key.Matches(msg, keys.Settings.Down):
if m.cursor < len(m.availableTerms)-1 {
m.cursor++
}
case key.Matches(msg, keys.Settings.Select):
if m.cursor < len(m.availableTerms) {
m.cfg.Terminal = m.availableTerms[m.cursor]
if err := m.cfg.Save(); err != nil {
m.statusMsg = fmt.Sprintf("error saving: %v", err)
} else {
m.statusMsg = fmt.Sprintf("Terminal set to %s", m.cfg.Terminal)
}
}
}
}
return m, nil
}View method · go · L61-L114 (54 LOC)internal/tui/screens/settings/settings.go
func (m Model) View() string {
title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Render("Settings")
subtitle := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("Terminal Emulator")
var sb strings.Builder
sb.WriteString(title + "\n")
sb.WriteString(subtitle + "\n\n")
if len(m.availableTerms) == 0 {
sb.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render(" (no terminals detected)") + "\n")
}
// Find current terminal index for highlighting
currentIdx := -1
for i, term := range m.availableTerms {
if term == m.cfg.Terminal {
currentIdx = i
break
}
}
for i, term := range m.availableTerms {
cursor := " "
check := " "
termStr := term
// Mark current selection
if i == currentIdx {
check = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("[✓]")
} else {
check = "[ ]"
}
// Highlight cursor position
if i == m.cursor {
cursor = "▸ "
termStr = lipgloss.NewStyle().Foregrpage 1 / 2next ›