TUI IDE multiplexer?!
golang tui ide
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add global hotkey system; add quit, restart, and various focus hotkey and bus handling

kettek defbf30c e3bb4b54

+225 -8
+117 -3
app.go
··· 6 6 7 7 "github.com/gdamore/tcell/v2" 8 8 "github.com/godbus/dbus/v5" 9 + "github.com/godbus/dbus/v5/introspect" 9 10 "github.com/rivo/tview" 10 11 ) 11 12 ··· 24 25 status *Status 25 26 // focusedPanel *Term 26 27 lastFocusedEditor int 28 + shellLastFocused bool 27 29 } 28 30 29 31 func NewApp() *App { ··· 31 33 Application: *tview.NewApplication(), 32 34 } 33 35 36 + // Hotkey and bus handling for quitting. 37 + hotkeys.Add(Hotkey{ 38 + Modifiers: tcell.ModAlt, 39 + Rune: 'q', 40 + Command: CMD_QUIT, 41 + }) 42 + bus.Subscribe(CMD_QUIT, func(v ...any) { 43 + app.Stop() 44 + }) 45 + 46 + // Hotkey and bus handling for restarting. 47 + hotkeys.Add(Hotkey{ 48 + Modifiers: tcell.ModAlt, 49 + Rune: 'r', 50 + Command: CMD_RESTART, 51 + }) 52 + bus.Subscribe(CMD_RESTART, func(v ...any) { 53 + app.Stop() 54 + if err := restartProcess(); err != nil { 55 + panic(err) 56 + } 57 + }) 58 + 59 + // Hotkey and bus handling for focusing panels 0-9. 60 + getPanelPath := func(num int) string { 61 + var count int 62 + 63 + if num == 0 { 64 + return "fm/active" 65 + } 66 + count++ 67 + 68 + for i := range app.editors.editors { 69 + if num == i+count { 70 + return fmt.Sprintf("editor/%d", i) 71 + } 72 + } 73 + count += len(app.editors.editors) 74 + for i := range app.shells.shells { 75 + if num == i+count { 76 + return fmt.Sprintf("shell/%d", i) 77 + } 78 + } 79 + 80 + // Out of bounds, just return blank. 81 + return "" 82 + } 83 + 84 + for i := range 9 { 85 + hotkeys.Add(Hotkey{ 86 + Modifiers: tcell.ModAlt, 87 + Rune: rune(48 + i), 88 + Command: CMD_FOCUS_PANEL, 89 + }) 90 + bus.Subscribe(CMD_FOCUS_PANEL, func(v ...any) { 91 + if hk, ok := v[0].(Hotkey); ok { 92 + if panelId := getPanelPath(int(hk.Rune) - 48 - 1); panelId != "" { 93 + if err := app.Conn.Object(app.dbusID, dbus.ObjectPath("/panels/"+panelId)).Call("SetFocus", dbus.FlagNoAutoStart).Store(); err != nil { 94 + panic(err) 95 + } 96 + } 97 + } 98 + }) 99 + } 100 + 101 + // Hotkey and bus handling for last editor <-> last shell focus toggling. 102 + hotkeys.Add(Hotkey{ 103 + Modifiers: tcell.ModCtrl, 104 + Key: tcell.KeyCtrlSpace, // ` 105 + Command: CMD_TOGGLE_SHELL_FOCUS, 106 + }) 107 + bus.Subscribe(CMD_TOGGLE_SHELL_FOCUS, func(v ...any) { 108 + if app.shellLastFocused { 109 + if err := app.Conn.Object(app.dbusID, dbus.ObjectPath("/panels/editor/active")).Call("SetFocus", dbus.FlagNoAutoStart).Store(); err != nil { 110 + panic(err) 111 + } 112 + } else { 113 + if err := app.Conn.Object(app.dbusID, dbus.ObjectPath("/panels/shell/active")).Call("SetFocus", dbus.FlagNoAutoStart).Store(); err != nil { 114 + panic(err) 115 + } 116 + } 117 + }) 118 + 34 119 app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 35 120 if event.Key() == tcell.KeyCtrlC { 36 121 return tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone) 37 122 } else if event.Key() == tcell.KeyEscape { 38 123 bus.Publish(UI_CANCEL, event) 39 124 } 40 - return event 125 + 126 + // Process hotkeys -- note that we could handle this logic in checkHotkeys itself but we should attempt to allow event key passthru. 127 + passthru := true 128 + if hks := hotkeys.CheckEventKey(event); hks != nil { 129 + for _, hk := range hks { 130 + if !hk.Passthru { 131 + passthru = false 132 + } 133 + bus.Publish(hk.Command, hk) 134 + } 135 + } 136 + 137 + if passthru { 138 + return event 139 + } 140 + 141 + return nil 41 142 }) 42 143 43 144 app.editors.env = &app.env 44 145 app.editors.setFocusFunc = func(p tview.Primitive) { 45 146 app.SetFocus(p) 147 + app.shellLastFocused = false 46 148 } 47 149 app.shells.env = &app.env 48 150 app.shells.setFocusFunc = func(p tview.Primitive) { 49 151 app.SetFocus(p) 152 + app.shellLastFocused = true 50 153 } 51 154 return app 52 155 } ··· 149 252 } 150 253 151 254 func (a *App) RunFM() error { 152 - a.fm = NewTerm("fm", cfg.FileManager) 255 + a.fm = NewTerm("fm/0", cfg.FileManager) 153 256 154 257 if err := app.Conn.ExportMethodTable(map[string]any{ 155 258 "SetFocus": func() *dbus.Error { 156 259 a.SetFocus(a.fm) 157 260 return nil 158 261 }, 159 - }, dbus.ObjectPath("/panels/fm"), "net.kettek.Aight.Panel"); err != nil { 262 + "SendString": func(str string) *dbus.Error { 263 + a.fm.SendString(str) 264 + return nil 265 + }, 266 + "SendBytes": func(b []byte) *dbus.Error { 267 + a.fm.SendBytes(b) 268 + return nil 269 + }, 270 + }, dbus.ObjectPath("/panels/fm/active"), "net.kettek.Aight.Panel"); err != nil { 271 + return err 272 + } 273 + if err := app.Conn.Export(introspect.Introspectable(termIntro), dbus.ObjectPath("/panels/fm/active"), "org.freedesktop.DBus.Introspectable"); err != nil { 160 274 return err 161 275 } 162 276
+1 -1
bus.go
··· 20 20 func (b *Bus) Publish(topic string, parts ...any) { 21 21 if t, ok := b.topics[topic]; ok { 22 22 for _, s := range t.subscriptions { 23 - s.callback(parts) 23 + s.callback(parts...) 24 24 } 25 25 } 26 26 }
+6
editors.go
··· 80 80 } 81 81 return nil 82 82 }, 83 + "SendString": func(s string) *dbus.Error { 84 + return editor.SendString(s) 85 + }, 86 + "SendBytes": func(b []byte) *dbus.Error { 87 + return editor.SendBytes(b) 88 + }, 83 89 }, dbus.ObjectPath(fmt.Sprintf("/panels/editor/%d", id)), "net.kettek.Aight.Panel"); err != nil { 84 90 return err 85 91 }
+2
go.sum
··· 20 20 github.com/gookit/goutil v0.7.1/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU= 21 21 github.com/gookit/ini/v2 v2.3.2 h1:W6tzOGE6zOLQelH2xhcH8BIBZPtnEpJgQ+J6SsAKBSw= 22 22 github.com/gookit/ini/v2 v2.3.2/go.mod h1:StKSqY5niArRwYBS8Z71+iWUt5ow47qt359sS9YQLYY= 23 + github.com/kettek/terminal v0.0.0-20260401081058-301e56decebd h1:4BWQSVrpAKBrQoV+b0N4QZrlXPjJxu49sNyoYoDuu8c= 24 + github.com/kettek/terminal v0.0.0-20260401081058-301e56decebd/go.mod h1:xOwMARel6Z8GtpaiN85N0ZHJpBMtE7VaTtJJwrNuWfc= 23 25 github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 24 26 github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 25 27 github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+46
hotkeys.go
··· 1 + package main 2 + 3 + import "github.com/gdamore/tcell/v2" 4 + 5 + type Hotkey struct { 6 + Modifiers tcell.ModMask 7 + Key tcell.Key 8 + Rune rune 9 + Command string // ??? 10 + Passthru bool 11 + } 12 + 13 + const ( 14 + CMD_QUIT = "cmd.quit" 15 + CMD_RESTART = "cmd.restart" 16 + CMD_FOCUS_PANEL = "cmd.focusPanel" 17 + CMD_FOCUS_SHELL = "cmd.focusShell" 18 + CMD_FOCUS_EDITOR = "cmd.focusEditor" 19 + CMD_FOCUS_FM = "cmd.focusFileManager" 20 + CMD_TOGGLE_SHELL_FOCUS = "cmd.toggleShellFocus" 21 + ) 22 + 23 + type Hotkeys []Hotkey 24 + 25 + func (hks *Hotkeys) Add(hk Hotkey) { 26 + *hks = append(*hks, hk) 27 + } 28 + 29 + func (hks *Hotkeys) CheckEventKey(event *tcell.EventKey) (results []Hotkey) { 30 + mod := event.Modifiers() 31 + key := event.Key() 32 + rune := event.Rune() 33 + for _, hk := range *hks { 34 + if hk.Modifiers&mod == 0 { 35 + continue 36 + } 37 + if hk.Rune != 0 && key == tcell.KeyRune && hk.Rune == rune { 38 + results = append(results, hk) 39 + } else if hk.Key != 0 && key != 0 { 40 + results = append(results, hk) 41 + } 42 + } 43 + return 44 + } 45 + 46 + var hotkeys Hotkeys
+17
restart_posix.go
··· 1 + //go:build unix 2 + 3 + package main 4 + 5 + import ( 6 + "os" 7 + "syscall" 8 + ) 9 + 10 + func restartProcess() error { 11 + exe, err := os.Executable() 12 + if err != nil { 13 + return err 14 + } 15 + 16 + return syscall.Exec(exe, os.Args, os.Environ()) 17 + }
+23
restart_win.go
··· 1 + //go:build windows 2 + 3 + package main 4 + 5 + import ( 6 + "os" 7 + "os/exec" 8 + ) 9 + 10 + func restart() error { 11 + exe, err := os.Executable() 12 + if err != nil { 13 + return err 14 + } 15 + 16 + cmd := exec.Command(exe, os.Args[1:]...) 17 + cmd.Stdout = os.Stdout 18 + cmd.Stderr = os.Stderr 19 + cmd.Stdin = os.Stdin 20 + cmd.Env = os.Environ() 21 + 22 + return cmd.Start() 23 + }
+13 -1
shells.go
··· 75 75 // Add focus handler. 76 76 if err := s.conn.ExportMethodTable(map[string]any{ 77 77 "SetFocus": func() *dbus.Error { 78 - if shell := s.GetShellByIndex(s.lastFocusedShell); shell != nil { 78 + if shell := s.GetShellByIndex(id); shell != nil { 79 79 s.setFocusFunc(shell) 80 + } 81 + return nil 82 + }, 83 + "SendString": func(s string) *dbus.Error { 84 + if _, err := shell.vt.File().WriteString(s); err != nil { 85 + return dbus.NewError("Send", []any{err.Error()}) 86 + } 87 + return nil 88 + }, 89 + "SendBytes": func(b []byte) *dbus.Error { 90 + if _, err := shell.vt.File().Write(b); err != nil { 91 + return dbus.NewError("Send", []any{err.Error()}) 80 92 } 81 93 return nil 82 94 },
-3
term.go
··· 59 59 } 60 60 61 61 func (t *Term) AddToConn(conn *dbus.Conn) error { 62 - if err := app.Conn.Export(t, dbus.ObjectPath("/panels/"+t.id), "net.kettek.Aight.Panel"); err != nil { 63 - panic(err) 64 - } 65 62 if err := app.Conn.Export(introspect.Introspectable(termIntro), dbus.ObjectPath("/panels/"+t.id), "org.freedesktop.DBus.Introspectable"); err != nil { 66 63 panic(err) 67 64 }