From b80c5190a3c20e8508cbdaf05e68790068892458 Mon Sep 17 00:00:00 2001 From: Syahdan Date: Wed, 15 Oct 2025 09:51:35 +0700 Subject: [PATCH] feat(chat): CLI flags, centered header; improve server with selective broadcast and quit handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client/main: - Add flags: -host (default "localhost:9000") and -server (run only server) - When -server is set, startTCPServer(host) and exit - Use provided host for client connection - Center header with width-aware lipgloss styling; adjust padding Server: - Introduce broadcast struct {text string, exclude net.Conn} - Hub.msgCh now chan broadcast; send messages with optional exclusion - On join: send “[join] …” to others (exclude the joiner) - Broadcast chat lines without exclusion; newline-delimited as before - Handle “/quit” by breaking loop (graceful disconnect) - On leave: broadcast “[leave] …” to all Refactor notes: - Remove unconditional embedded server from client startup - Scanner loop unchanged aside from quit handling and broadcast usage --- main.go | 21 ++++++++++++++++----- server.go | 23 +++++++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index c6837fc..1bc92cb 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "errors" + "flag" "fmt" "io" "net" @@ -179,7 +180,7 @@ func (m *model) refreshViewport() { } func (m model) View() string { - header := lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Bold(true).Render(m.server) + header := lipgloss.NewStyle().Width(m.vp.Width).Padding(0, 1).Align(lipgloss.Center).Foreground(lipgloss.Color("241")).Bold(true).Render(m.server) return lipgloss.JoinVertical( lipgloss.Left, header, @@ -189,15 +190,25 @@ func (m model) View() string { } func main() { - go func() { - if err := startTCPServer("localhost:9000"); err != nil { + var ( + host string + serverOnly bool + ) + + flag.StringVar(&host, "host", "localhost:9000", "host:port to connect to or bind the server on") + flag.BoolVar(&serverOnly, "server", false, "run only the server") + flag.Parse() + + if serverOnly { + if err := startTCPServer(host); err != nil { fmt.Println("Server error:", err) } - }() + return + } time.Sleep(200 * time.Millisecond) - p := tea.NewProgram(initialModel("127.0.0.1:9000"), tea.WithAltScreen(), tea.WithMouseCellMotion()) + p := tea.NewProgram(initialModel(host), tea.WithAltScreen(), tea.WithMouseCellMotion()) if _, err := p.Run(); err != nil { fmt.Println("Error:", err) } diff --git a/server.go b/server.go index a76cfcc..4e09981 100644 --- a/server.go +++ b/server.go @@ -9,12 +9,17 @@ import ( "sync" ) +type broadcast struct { + text string + exclude net.Conn +} + type Hub struct { mu sync.Mutex conns map[net.Conn]struct{} joinCh chan net.Conn leaveCh chan net.Conn - msgCh chan string + msgCh chan broadcast } func NewHub() *Hub { @@ -22,7 +27,7 @@ func NewHub() *Hub { conns: make(map[net.Conn]struct{}), joinCh: make(chan net.Conn), leaveCh: make(chan net.Conn), - msgCh: make(chan string, 128), + msgCh: make(chan broadcast, 128), } } @@ -43,8 +48,10 @@ func (h *Hub) Run() { case msg := <-h.msgCh: h.mu.Lock() for c := range h.conns { - // newline-delimited messages - fmt.Fprintln(c, msg) + if msg.exclude != nil && c == msg.exclude { + continue + } + fmt.Fprintln(c, msg.text) } h.mu.Unlock() } @@ -57,7 +64,7 @@ func handleConn(h *Hub, c net.Conn) { name := c.RemoteAddr().String() fmt.Fprintf(c, "Welcome %s\n", name) - h.msgCh <- fmt.Sprintf("[join] %s", name) + h.msgCh <- broadcast{text: fmt.Sprintf("[join] %s", name), exclude: c} scanner := bufio.NewScanner(c) scanner.Buffer(make([]byte, 0, 1024), 64*1024) @@ -67,14 +74,14 @@ func handleConn(h *Hub, c net.Conn) { continue } if line == "/quit" { - return + break } - h.msgCh <- fmt.Sprintf("%s: %s", name, line) + h.msgCh <- broadcast{text: fmt.Sprintf("%s: %s", name, line)} } if err := scanner.Err(); err != nil { log.Printf("read err from %s: %v", name, err) } - h.msgCh <- fmt.Sprintf("[leave] %s", name) + h.msgCh <- broadcast{text: fmt.Sprintf("[leave] %s", name)} } func startTCPServer(addr string) error {