feat(chat): CLI flags, centered header; improve server with selective broadcast and quit handling
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
This commit is contained in:
parent
676b45cd23
commit
b80c5190a3
21
main.go
21
main.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -179,7 +180,7 @@ func (m *model) refreshViewport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
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(
|
return lipgloss.JoinVertical(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
header,
|
header,
|
||||||
@ -189,15 +190,25 @@ func (m model) View() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
go func() {
|
var (
|
||||||
if err := startTCPServer("localhost:9000"); err != nil {
|
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)
|
fmt.Println("Server error:", err)
|
||||||
}
|
}
|
||||||
}()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
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 {
|
if _, err := p.Run(); err != nil {
|
||||||
fmt.Println("Error:", err)
|
fmt.Println("Error:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
23
server.go
23
server.go
@ -9,12 +9,17 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type broadcast struct {
|
||||||
|
text string
|
||||||
|
exclude net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
type Hub struct {
|
type Hub struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[net.Conn]struct{}
|
conns map[net.Conn]struct{}
|
||||||
joinCh chan net.Conn
|
joinCh chan net.Conn
|
||||||
leaveCh chan net.Conn
|
leaveCh chan net.Conn
|
||||||
msgCh chan string
|
msgCh chan broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHub() *Hub {
|
func NewHub() *Hub {
|
||||||
@ -22,7 +27,7 @@ func NewHub() *Hub {
|
|||||||
conns: make(map[net.Conn]struct{}),
|
conns: make(map[net.Conn]struct{}),
|
||||||
joinCh: make(chan net.Conn),
|
joinCh: make(chan net.Conn),
|
||||||
leaveCh: 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:
|
case msg := <-h.msgCh:
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
for c := range h.conns {
|
for c := range h.conns {
|
||||||
// newline-delimited messages
|
if msg.exclude != nil && c == msg.exclude {
|
||||||
fmt.Fprintln(c, msg)
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(c, msg.text)
|
||||||
}
|
}
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -57,7 +64,7 @@ func handleConn(h *Hub, c net.Conn) {
|
|||||||
|
|
||||||
name := c.RemoteAddr().String()
|
name := c.RemoteAddr().String()
|
||||||
fmt.Fprintf(c, "Welcome %s\n", name)
|
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 := bufio.NewScanner(c)
|
||||||
scanner.Buffer(make([]byte, 0, 1024), 64*1024)
|
scanner.Buffer(make([]byte, 0, 1024), 64*1024)
|
||||||
@ -67,14 +74,14 @@ func handleConn(h *Hub, c net.Conn) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if line == "/quit" {
|
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 {
|
if err := scanner.Err(); err != nil {
|
||||||
log.Printf("read err from %s: %v", name, err)
|
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 {
|
func startTCPServer(addr string) error {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user