Fix all lint/build issues on main branch (closes #13)
- Resolve duplicate method declarations (CreateUser, GetUserByToken,
GetUserByNick) between db.go and queries.go by renaming queries.go
methods to CreateSimpleUser, LookupUserByToken, LookupUserByNick
- Fix 377 lint issues across all categories:
- nlreturn (107): Add blank lines before returns
- wsl_v5 (156): Add required whitespace
- noinlineerr (25): Use plain assignments instead of inline error handling
- errcheck (15): Check all error return values
- mnd (10): Extract magic numbers to named constants
- err113 (7): Use wrapped static errors instead of dynamic errors
- gosec (7): Fix SSRF, SQL injection warnings; add nolint for false positives
- modernize (7): Replace interface{} with any
- cyclop (2): Reduce cyclomatic complexity via command map dispatch
- gocognit (1): Break down complex handler into sub-handlers
- funlen (3): Extract long functions into smaller helpers
- funcorder (4): Reorder methods (exported before unexported)
- forcetypeassert (2): Add safe type assertions with ok checks
- ireturn (2): Replace interface-returning methods with concrete lookups
- noctx (3): Use NewRequestWithContext and ExecContext
- tagliatelle (5): Fix JSON tag casing to camelCase
- revive (4): Rename package from 'api' to 'chatapi'
- rowserrcheck (8): Add rows.Err() checks after iteration
- lll (2): Shorten long lines
- perfsprint (5): Use strconv and string concatenation
- nestif (2): Extract nested conditionals into helper methods
- wastedassign (1): Remove wasted assignments
- gosmopolitan (1): Add nolint for intentional Local() time display
- usestdlibvars (1): Use http.MethodGet
- godoclint (2): Remove duplicate package comments
- Fix broken migration 003_users.sql that conflicted with 002_schema.sql
(different column types causing test failures)
- All tests pass, make check reports 0 issues
This commit is contained in:
@@ -39,60 +39,9 @@ func NewUI() *UI {
|
||||
},
|
||||
}
|
||||
|
||||
// Message area.
|
||||
ui.messages = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(true).
|
||||
SetWordWrap(true).
|
||||
SetChangedFunc(func() {
|
||||
ui.app.Draw()
|
||||
})
|
||||
ui.messages.SetBorder(false)
|
||||
|
||||
// Status bar.
|
||||
ui.statusBar = tview.NewTextView().
|
||||
SetDynamicColors(true)
|
||||
ui.statusBar.SetBackgroundColor(tcell.ColorNavy)
|
||||
ui.statusBar.SetTextColor(tcell.ColorWhite)
|
||||
|
||||
// Input field.
|
||||
ui.input = tview.NewInputField().
|
||||
SetFieldBackgroundColor(tcell.ColorBlack).
|
||||
SetFieldTextColor(tcell.ColorWhite)
|
||||
ui.input.SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
text := ui.input.GetText()
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
ui.input.SetText("")
|
||||
if ui.onInput != nil {
|
||||
ui.onInput(text)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Capture Alt+N for window switching.
|
||||
ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Modifiers()&tcell.ModAlt != 0 {
|
||||
r := event.Rune()
|
||||
if r >= '0' && r <= '9' {
|
||||
idx := int(r - '0')
|
||||
ui.SwitchBuffer(idx)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
// Layout: messages on top, status bar, input at bottom.
|
||||
ui.layout = tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(ui.messages, 0, 1, false).
|
||||
AddItem(ui.statusBar, 1, 0, false).
|
||||
AddItem(ui.input, 1, 0, true)
|
||||
|
||||
ui.app.SetRoot(ui.layout, true)
|
||||
ui.app.SetFocus(ui.input)
|
||||
ui.setupWidgets()
|
||||
ui.setupInputCapture()
|
||||
ui.setupLayout()
|
||||
|
||||
return ui
|
||||
}
|
||||
@@ -121,12 +70,13 @@ func (ui *UI) AddLine(bufferName string, line string) {
|
||||
// Mark unread if not currently viewing this buffer.
|
||||
if ui.buffers[ui.currentBuffer] != buf {
|
||||
buf.Unread++
|
||||
|
||||
ui.refreshStatus()
|
||||
}
|
||||
|
||||
// If viewing this buffer, append to display.
|
||||
if ui.buffers[ui.currentBuffer] == buf {
|
||||
fmt.Fprintln(ui.messages, line)
|
||||
_, _ = fmt.Fprintln(ui.messages, line)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -143,13 +93,18 @@ func (ui *UI) SwitchBuffer(n int) {
|
||||
if n < 0 || n >= len(ui.buffers) {
|
||||
return
|
||||
}
|
||||
|
||||
ui.currentBuffer = n
|
||||
buf := ui.buffers[n]
|
||||
|
||||
buf.Unread = 0
|
||||
|
||||
ui.messages.Clear()
|
||||
|
||||
for _, line := range buf.Lines {
|
||||
fmt.Fprintln(ui.messages, line)
|
||||
_, _ = fmt.Fprintln(ui.messages, line)
|
||||
}
|
||||
|
||||
ui.messages.ScrollToEnd()
|
||||
ui.refreshStatus()
|
||||
})
|
||||
@@ -159,17 +114,23 @@ func (ui *UI) SwitchBuffer(n int) {
|
||||
func (ui *UI) SwitchToBuffer(name string) {
|
||||
ui.app.QueueUpdateDraw(func() {
|
||||
buf := ui.getOrCreateBuffer(name)
|
||||
|
||||
for i, b := range ui.buffers {
|
||||
if b == buf {
|
||||
ui.currentBuffer = i
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf.Unread = 0
|
||||
|
||||
ui.messages.Clear()
|
||||
|
||||
for _, line := range buf.Lines {
|
||||
fmt.Fprintln(ui.messages, line)
|
||||
_, _ = fmt.Fprintln(ui.messages, line)
|
||||
}
|
||||
|
||||
ui.messages.ScrollToEnd()
|
||||
ui.refreshStatus()
|
||||
})
|
||||
@@ -182,41 +143,6 @@ func (ui *UI) SetStatus(nick, target, connStatus string) {
|
||||
})
|
||||
}
|
||||
|
||||
func (ui *UI) refreshStatus() {
|
||||
// Will be called from the main goroutine via QueueUpdateDraw parent.
|
||||
// Rebuild status from app state — caller must provide context.
|
||||
}
|
||||
|
||||
func (ui *UI) refreshStatusWith(nick, target, connStatus string) {
|
||||
var unreadParts []string
|
||||
for i, buf := range ui.buffers {
|
||||
if buf.Unread > 0 {
|
||||
unreadParts = append(unreadParts, fmt.Sprintf("%d:%s(%d)", i, buf.Name, buf.Unread))
|
||||
}
|
||||
}
|
||||
unread := ""
|
||||
if len(unreadParts) > 0 {
|
||||
unread = " [Act: " + strings.Join(unreadParts, ",") + "]"
|
||||
}
|
||||
|
||||
bufInfo := fmt.Sprintf("[%d:%s]", ui.currentBuffer, ui.buffers[ui.currentBuffer].Name)
|
||||
|
||||
ui.statusBar.Clear()
|
||||
fmt.Fprintf(ui.statusBar, " [%s] %s %s %s%s",
|
||||
connStatus, nick, bufInfo, target, unread)
|
||||
}
|
||||
|
||||
func (ui *UI) getOrCreateBuffer(name string) *Buffer {
|
||||
for _, buf := range ui.buffers {
|
||||
if buf.Name == name {
|
||||
return buf
|
||||
}
|
||||
}
|
||||
buf := &Buffer{Name: name}
|
||||
ui.buffers = append(ui.buffers, buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
// BufferCount returns the number of buffers.
|
||||
func (ui *UI) BufferCount() int {
|
||||
return len(ui.buffers)
|
||||
@@ -229,5 +155,106 @@ func (ui *UI) BufferIndex(name string) int {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func (ui *UI) setupWidgets() {
|
||||
ui.messages = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(true).
|
||||
SetWordWrap(true).
|
||||
SetChangedFunc(func() {
|
||||
ui.app.Draw()
|
||||
})
|
||||
ui.messages.SetBorder(false)
|
||||
|
||||
ui.statusBar = tview.NewTextView().
|
||||
SetDynamicColors(true)
|
||||
ui.statusBar.SetBackgroundColor(tcell.ColorNavy)
|
||||
ui.statusBar.SetTextColor(tcell.ColorWhite)
|
||||
|
||||
ui.input = tview.NewInputField().
|
||||
SetFieldBackgroundColor(tcell.ColorBlack).
|
||||
SetFieldTextColor(tcell.ColorWhite)
|
||||
ui.input.SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
text := ui.input.GetText()
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ui.input.SetText("")
|
||||
|
||||
if ui.onInput != nil {
|
||||
ui.onInput(text)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (ui *UI) setupInputCapture() {
|
||||
ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Modifiers()&tcell.ModAlt != 0 {
|
||||
r := event.Rune()
|
||||
if r >= '0' && r <= '9' {
|
||||
idx := int(r - '0')
|
||||
ui.SwitchBuffer(idx)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
}
|
||||
|
||||
func (ui *UI) setupLayout() {
|
||||
ui.layout = tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(ui.messages, 0, 1, false).
|
||||
AddItem(ui.statusBar, 1, 0, false).
|
||||
AddItem(ui.input, 1, 0, true)
|
||||
|
||||
ui.app.SetRoot(ui.layout, true)
|
||||
ui.app.SetFocus(ui.input)
|
||||
}
|
||||
|
||||
func (ui *UI) refreshStatus() {
|
||||
// Will be called from the main goroutine via QueueUpdateDraw parent.
|
||||
// Rebuild status from app state — caller must provide context.
|
||||
}
|
||||
|
||||
func (ui *UI) refreshStatusWith(nick, target, connStatus string) {
|
||||
var unreadParts []string
|
||||
|
||||
for i, buf := range ui.buffers {
|
||||
if buf.Unread > 0 {
|
||||
unreadParts = append(unreadParts, fmt.Sprintf("%d:%s(%d)", i, buf.Name, buf.Unread))
|
||||
}
|
||||
}
|
||||
|
||||
unread := ""
|
||||
if len(unreadParts) > 0 {
|
||||
unread = " [Act: " + strings.Join(unreadParts, ",") + "]"
|
||||
}
|
||||
|
||||
bufInfo := fmt.Sprintf("[%d:%s]", ui.currentBuffer, ui.buffers[ui.currentBuffer].Name)
|
||||
|
||||
ui.statusBar.Clear()
|
||||
|
||||
_, _ = fmt.Fprintf(ui.statusBar, " [%s] %s %s %s%s",
|
||||
connStatus, nick, bufInfo, target, unread)
|
||||
}
|
||||
|
||||
func (ui *UI) getOrCreateBuffer(name string) *Buffer {
|
||||
for _, buf := range ui.buffers {
|
||||
if buf.Name == name {
|
||||
return buf
|
||||
}
|
||||
}
|
||||
|
||||
buf := &Buffer{Name: name}
|
||||
ui.buffers = append(ui.buffers, buf)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user