tangled
alpha
login
or
join now
moth11.net
/
ttyxcvr
0
fork
atom
xcvr tui
0
fork
atom
overview
issues
pulls
pipelines
some refactors etc
moth11.net
5 months ago
377d6390
6aad60ae
+410
-306
1 changed file
expand all
collapse all
unified
split
main.go
+410
-306
main.go
···
51
51
ChannelList
52
52
ResolvingChannel
53
53
ConnectingToChannel
54
54
+
DialingChannel
54
55
Connected
55
56
)
56
57
···
58
59
59
60
const (
60
61
Normal txmode = iota
61
61
-
Command
62
62
Insert
63
63
)
64
64
65
65
type model struct {
66
66
-
state txstate
67
67
-
mode txmode
68
68
-
width int
69
69
-
height int
70
70
-
error *error
71
71
-
prompt textinput.Model
72
72
-
draft *textinput.Model
73
73
-
sentmsg *string
74
74
-
channels *[]Channel
75
75
-
list *list.Model
76
76
-
curchannel *Channel
77
77
-
wsurl *string
78
78
-
lrcconn *websocket.Conn
79
79
-
lexconn *websocket.Conn
80
80
-
evtchan chan []byte
81
81
-
cancel func()
82
82
-
vp *viewport.Model
83
83
-
msgs map[uint32]*Message
84
84
-
myid *uint32
85
85
-
renders []*string
86
86
-
topic *string
87
87
-
color *uint32
88
88
-
nick *string
89
89
-
handle *string
90
90
-
signeturi *string
91
91
-
xrpc *PasswordClient
66
66
+
cmding bool
67
67
+
cmdout *string
68
68
+
error *error
69
69
+
prompt textinput.Model
70
70
+
clm *channellistmodel
71
71
+
cm *channelmodel
72
72
+
gsd *globalsettingsdata
73
73
+
}
74
74
+
75
75
+
type channellistmodel struct {
76
76
+
channels []Channel
77
77
+
list list.Model
78
78
+
gsd *globalsettingsdata
79
79
+
}
80
80
+
81
81
+
type channelmodel struct {
82
82
+
channel Channel
83
83
+
mode txmode
84
84
+
wsurl string
85
85
+
lrcconn *websocket.Conn
86
86
+
lexconn *websocket.Conn
87
87
+
cancel func()
88
88
+
vp viewport.Model
89
89
+
draft textinput.Model
90
90
+
msgs map[uint32]*Message
91
91
+
myid *uint32
92
92
+
render []*string
93
93
+
sentmsg *string
94
94
+
topic *string
95
95
+
signeturi *string
96
96
+
datachan chan []byte
97
97
+
gsd *globalsettingsdata
98
98
+
}
99
99
+
100
100
+
type globalsettingsdata struct {
101
101
+
color *uint32
102
102
+
nick *string
103
103
+
handle *string
104
104
+
xrpc *PasswordClient
105
105
+
width int
106
106
+
height int
107
107
+
state txstate
92
108
}
93
109
94
110
type Message struct {
···
187
203
func initialModel() model {
188
204
prompt := textinput.New()
189
205
prompt.Prompt = ":"
206
206
+
prompt.Width = 28 //: + prompt.Width + 1 left over for blinky = initialWidth
190
207
nick := "wanderer"
191
208
color := uint32(33096)
192
192
-
return model{
209
209
+
gsd := globalsettingsdata{
210
210
+
nick: &nick,
211
211
+
color: &color,
212
212
+
width: 30,
213
213
+
height: 20,
193
214
state: Splash,
194
194
-
mode: Normal,
215
215
+
}
216
216
+
return model{
195
217
prompt: prompt,
196
196
-
width: 30,
197
197
-
height: 20,
198
198
-
nick: &nick,
199
199
-
color: &color,
218
218
+
gsd: &gsd,
200
219
}
201
220
}
202
221
func (m model) Init() tea.Cmd {
···
210
229
case "q":
211
230
return m, tea.Quit
212
231
default:
213
213
-
m.state = GettingChannels
232
232
+
m.gsd.state = GettingChannels
214
233
return m, GetChannels
215
234
}
216
235
}
···
265
284
xrpc *PasswordClient
266
285
}
267
286
287
287
+
func (cm *channelmodel) updateLRCIdentity() {
288
288
+
if cm != nil && cm.lrcconn != nil {
289
289
+
err := sendSet(cm.datachan, cm.gsd.nick, cm.gsd.handle, cm.gsd.color)
290
290
+
if err != nil {
291
291
+
send(errMsg{err})
292
292
+
}
293
293
+
}
294
294
+
}
295
295
+
268
296
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
269
297
switch msg := msg.(type) {
298
298
+
case tea.KeyMsg:
299
299
+
if msg.String() == "ctrl+c" {
300
300
+
return m, tea.Quit
301
301
+
}
302
302
+
if m.cmdout != nil {
303
303
+
m.cmdout = nil
304
304
+
return m, nil
305
305
+
}
306
306
+
if (m.cm != nil && m.cm.mode == Insert) || (m.clm != nil && m.clm.list.FilterState() == list.Filtering) {
307
307
+
break
308
308
+
}
309
309
+
if !m.cmding {
310
310
+
if msg.String() == ":" {
311
311
+
m.cmding = true
312
312
+
return m, m.prompt.Focus()
313
313
+
}
314
314
+
} else {
315
315
+
switch msg.String() {
316
316
+
case "esc":
317
317
+
m.cmding = false
318
318
+
m.prompt.Blur()
319
319
+
m.prompt.SetValue("")
320
320
+
return m, nil
321
321
+
case "enter":
322
322
+
m.cmding = false
323
323
+
m.prompt.Blur()
324
324
+
v := m.prompt.Value()
325
325
+
m.prompt.SetValue("")
326
326
+
return m, m.evaluateCommand(v)
327
327
+
default:
328
328
+
p, cmd := m.prompt.Update(msg)
329
329
+
m.prompt = p
330
330
+
return m, cmd
331
331
+
}
332
332
+
}
270
333
case errMsg:
271
271
-
m.state = Error
334
334
+
m.gsd.state = Error
272
335
m.error = &msg.err
273
336
return m, nil
274
337
case svMsg:
275
275
-
if m.myid != nil && msg.signetView.LrcId == *m.myid {
276
276
-
m.signeturi = &msg.signetView.URI
338
338
+
if m.cm != nil && m.cm.myid != nil && msg.signetView.LrcId == *m.cm.myid {
339
339
+
m.cm.signeturi = &msg.signetView.URI
277
340
return m, nil
278
341
}
342
342
+
case dialMsg:
343
343
+
if len(msg.value) == 1 {
344
344
+
m.gsd.state = DialingChannel
345
345
+
return m, m.dialingChannel(msg.value)
346
346
+
}
279
347
280
348
case loginMsg:
281
349
if len(msg.value) == 2 {
282
350
return m, login(msg.value[0], msg.value[1])
283
351
}
284
352
case loggedInMsg:
285
285
-
m.xrpc = msg.xrpc
353
353
+
m.gsd.xrpc = msg.xrpc
286
354
return m, nil
287
355
288
356
case setMsg:
···
292
360
}
293
361
switch key {
294
362
case "color", "c":
295
295
-
i, err := strconv.Atoi(val)
296
296
-
if err != nil {
297
297
-
return m, nil
298
298
-
}
299
299
-
b := uint32(i)
300
300
-
m.color = &b
301
301
-
if m.draft != nil {
302
302
-
m.draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(&b))
363
363
+
var b uint32
364
364
+
365
365
+
if len(val) == 7 && val[0] == '#' {
366
366
+
b64, err := strconv.ParseUint(val[1:], 16, 0)
367
367
+
if err != nil {
368
368
+
return m, nil
369
369
+
}
370
370
+
b = uint32(b64)
371
371
+
} else {
372
372
+
i, err := strconv.Atoi(val)
373
373
+
if err != nil {
374
374
+
return m, nil
375
375
+
}
376
376
+
b = uint32(i)
303
377
}
304
304
-
err = sendSet(m.evtchan, m.nick, m.handle, m.color)
305
305
-
if err != nil {
306
306
-
send(errMsg{err})
378
378
+
m.gsd.color = &b
379
379
+
if m.cm != nil {
380
380
+
m.cm.draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(&b))
307
381
}
382
382
+
m.cm.updateLRCIdentity()
308
383
return m, nil
309
384
case "nick", "name", "n":
310
310
-
m.nick = &val
311
311
-
if m.draft != nil {
312
312
-
m.draft.Prompt = renderName(m.nick, m.handle) + " "
313
313
-
m.draft.Width = m.width - len(m.draft.Prompt) - 1
314
314
-
}
315
315
-
err := sendSet(m.evtchan, m.nick, m.handle, m.color)
316
316
-
if err != nil {
317
317
-
send(errMsg{err})
385
385
+
m.gsd.nick = &val
386
386
+
if m.cm != nil {
387
387
+
m.cm.draft.Prompt = renderName(m.gsd.nick, m.gsd.handle) + " "
388
388
+
m.cm.draft.Width = m.gsd.width - len(m.cm.draft.Prompt) - 1
318
389
}
390
390
+
m.cm.updateLRCIdentity()
319
391
return m, nil
320
392
case "handle", "h", "at", "@":
321
321
-
m.handle = &val
322
322
-
if m.draft != nil {
323
323
-
m.draft.Prompt = renderName(m.nick, m.handle) + " "
324
324
-
m.draft.Width = m.width - len(m.draft.Prompt) - 1
393
393
+
m.gsd.handle = &val
394
394
+
if m.cm != nil {
395
395
+
m.cm.draft.Prompt = renderName(m.gsd.nick, m.gsd.handle) + " "
396
396
+
m.cm.draft.Width = m.gsd.width - len(m.cm.draft.Prompt) - 1
325
397
}
326
326
-
err := sendSet(m.evtchan, m.nick, m.handle, m.color)
327
327
-
if err != nil {
328
328
-
send(errMsg{err})
329
329
-
}
398
398
+
m.cm.updateLRCIdentity()
330
399
return m, nil
331
400
}
332
401
333
402
case tea.WindowSizeMsg:
334
334
-
m.height = msg.Height
335
335
-
m.width = msg.Width
336
336
-
if m.vp != nil {
337
337
-
m.vp.Width = msg.Width
338
338
-
m.vp.Height = msg.Height - 2
403
403
+
m.gsd.height = msg.Height
404
404
+
m.gsd.width = msg.Width
405
405
+
m.prompt.Width = msg.Width - 2
406
406
+
if m.clm != nil {
407
407
+
m.clm.list.SetSize(msg.Width, msg.Height-1)
339
408
}
340
340
-
if m.draft != nil {
341
341
-
m.draft.Width = m.width - len(m.draft.Prompt) - 1
342
342
-
}
343
343
-
if m.renders != nil {
344
344
-
for _, message := range m.msgs {
345
345
-
message.renderMessage(msg.Width)
409
409
+
if m.cm != nil {
410
410
+
m.cm.vp.Width = msg.Width
411
411
+
m.cm.vp.Height = msg.Height - 2
412
412
+
m.cm.draft.Width = m.gsd.width - len(m.cm.draft.Prompt) - 1
413
413
+
if m.cm.render != nil {
414
414
+
for _, message := range m.cm.msgs {
415
415
+
message.renderMessage(msg.Width)
416
416
+
}
417
417
+
m.cm.vp.SetContent(JoinDeref(m.cm.render, ""))
346
418
}
347
347
-
m.vp.SetContent(JoinDeref(m.renders, ""))
348
348
-
}
349
349
-
if m.list != nil {
350
350
-
m.list.SetSize(msg.Width, msg.Height)
351
419
}
352
420
return m, nil
353
353
-
354
354
-
case tea.KeyMsg:
355
355
-
switch msg.String() {
356
356
-
case "ctrl+c":
357
357
-
return m, tea.Quit
358
358
-
}
359
421
}
360
422
361
361
-
switch m.state {
423
423
+
switch m.gsd.state {
362
424
case Splash:
363
425
return m.updateSplash(msg)
364
426
case GettingChannels:
365
427
return m.updateGettingChannels(msg)
366
428
case ChannelList:
367
367
-
return m.updateChannelList(msg)
429
429
+
clm, cmd, err := m.clm.updateChannelList(msg)
430
430
+
if err != nil {
431
431
+
m.gsd.state = Error
432
432
+
m.error = &err
433
433
+
return m, nil
434
434
+
}
435
435
+
m.clm = &clm
436
436
+
return m, cmd
368
437
case ResolvingChannel:
369
438
return m.updateResolvingChannel(msg)
370
439
case ConnectingToChannel:
371
440
return m.updateConnectingToChannel(msg)
441
441
+
case DialingChannel:
442
442
+
372
443
case Connected:
373
373
-
return m.updateConnected(msg)
444
444
+
cm, cmd, err := m.cm.updateConnected(msg)
445
445
+
if err != nil {
446
446
+
m.gsd.state = Error
447
447
+
m.error = &err
448
448
+
return m, nil
449
449
+
}
450
450
+
m.cm = &cm
451
451
+
return m, cmd
374
452
}
375
453
376
454
return m, nil
377
455
}
378
456
379
379
-
func (m model) updateConnected(msg tea.Msg) (tea.Model, tea.Cmd) {
457
457
+
func (cm channelmodel) updateConnected(msg tea.Msg) (channelmodel, tea.Cmd, error) {
380
458
switch msg := msg.(type) {
381
459
case lrcEvent:
382
460
if msg.e == nil {
383
383
-
m.state = Error
384
384
-
err := errors.New("nil lrcEvent")
385
385
-
m.error = &err
386
386
-
return m, nil
461
461
+
return cm, nil, errors.New("nil lrcEvent")
387
462
}
388
463
id := msg.e.Id
389
464
switch msg := msg.e.Msg.(type) {
390
465
case *lrcpb.Event_Ping:
391
391
-
return m, nil
466
466
+
return cm, nil, nil
392
467
case *lrcpb.Event_Pong:
393
393
-
return m, nil
468
468
+
return cm, nil, nil
394
469
case *lrcpb.Event_Init:
395
395
-
err := initMessage(msg.Init, m.msgs, &m.renders, m.width)
470
470
+
err := initMessage(msg.Init, cm.msgs, &cm.render, cm.gsd.width)
396
471
if err != nil {
397
397
-
m.state = Error
398
398
-
m.error = &err
399
399
-
return m, nil
472
472
+
return cm, nil, err
400
473
}
401
474
if msg.Init.Echoed != nil && *msg.Init.Echoed {
402
402
-
m.myid = msg.Init.Id
475
475
+
cm.myid = msg.Init.Id
403
476
}
404
404
-
ab := m.vp.AtBottom()
405
405
-
m.vp.SetContent(JoinDeref(m.renders, ""))
477
477
+
ab := cm.vp.AtBottom()
478
478
+
cm.vp.SetContent(JoinDeref(cm.render, ""))
406
479
if ab {
407
407
-
m.vp.GotoBottom()
480
480
+
cm.vp.GotoBottom()
408
481
}
409
409
-
return m, nil
482
482
+
return cm, nil, nil
410
483
case *lrcpb.Event_Pub:
411
411
-
err := pubMessage(msg.Pub, m.msgs, m.width)
484
484
+
err := pubMessage(msg.Pub, cm.msgs, cm.gsd.width)
412
485
if err != nil {
413
413
-
m.state = Error
414
414
-
m.error = &err
415
415
-
return m, nil
486
486
+
return cm, nil, err
416
487
}
417
417
-
m.vp.SetContent(JoinDeref(m.renders, ""))
418
418
-
return m, nil
488
488
+
cm.vp.SetContent(JoinDeref(cm.render, ""))
489
489
+
return cm, nil, err
419
490
case *lrcpb.Event_Insert:
420
420
-
err := insertMessage(msg.Insert, m.msgs, &m.renders, m.width)
491
491
+
err := insertMessage(msg.Insert, cm.msgs, &cm.render, cm.gsd.width)
421
492
if err != nil {
422
422
-
m.state = Error
423
423
-
m.error = &err
424
424
-
return m, nil
493
493
+
return cm, nil, err
425
494
}
426
426
-
ab := m.vp.AtBottom()
427
427
-
m.vp.SetContent(JoinDeref(m.renders, ""))
495
495
+
ab := cm.vp.AtBottom()
496
496
+
cm.vp.SetContent(JoinDeref(cm.render, ""))
428
497
if ab {
429
429
-
m.vp.GotoBottom()
498
498
+
cm.vp.GotoBottom()
430
499
}
431
431
-
return m, nil
500
500
+
return cm, nil, nil
432
501
case *lrcpb.Event_Delete:
433
433
-
err := deleteMessage(msg.Delete, m.msgs, &m.renders, m.width)
502
502
+
err := deleteMessage(msg.Delete, cm.msgs, &cm.render, cm.gsd.width)
434
503
if err != nil {
435
435
-
m.state = Error
436
436
-
m.error = &err
437
437
-
return m, nil
504
504
+
return cm, nil, err
438
505
}
439
439
-
ab := m.vp.AtBottom()
440
440
-
m.vp.SetContent(JoinDeref(m.renders, ""))
506
506
+
ab := cm.vp.AtBottom()
507
507
+
cm.vp.SetContent(JoinDeref(cm.render, ""))
441
508
if ab {
442
442
-
m.vp.GotoBottom()
509
509
+
cm.vp.GotoBottom()
443
510
}
444
444
-
return m, nil
511
511
+
return cm, nil, nil
445
512
case *lrcpb.Event_Mute:
446
446
-
return m, nil
513
513
+
return cm, nil, nil
447
514
case *lrcpb.Event_Unmute:
448
448
-
return m, nil
515
515
+
return cm, nil, nil
449
516
case *lrcpb.Event_Set:
450
450
-
return m, nil
517
517
+
return cm, nil, nil
451
518
case *lrcpb.Event_Get:
452
519
if msg.Get.Topic != nil {
453
453
-
m.topic = msg.Get.Topic
520
520
+
cm.topic = msg.Get.Topic
454
521
}
455
455
-
return m, nil
522
522
+
return cm, nil, nil
456
523
case *lrcpb.Event_Editbatch:
457
524
if id == nil {
458
458
-
return m, nil
525
525
+
return cm, nil, nil
459
526
}
460
460
-
err := editMessage(*id, msg.Editbatch.Edits, m.msgs, &m.renders, m.width)
527
527
+
err := editMessage(*id, msg.Editbatch.Edits, cm.msgs, &cm.render, cm.gsd.width)
461
528
if err != nil {
462
462
-
m.state = Error
463
463
-
m.error = &err
464
464
-
return m, nil
529
529
+
return cm, nil, err
465
530
}
466
466
-
ab := m.vp.AtBottom()
467
467
-
m.vp.SetContent(JoinDeref(m.renders, ""))
531
531
+
ab := cm.vp.AtBottom()
532
532
+
cm.vp.SetContent(JoinDeref(cm.render, ""))
468
533
if ab {
469
469
-
m.vp.GotoBottom()
534
534
+
cm.vp.GotoBottom()
470
535
}
471
471
-
return m, nil
536
536
+
return cm, nil, nil
472
537
}
473
538
case tea.KeyMsg:
474
474
-
switch m.mode {
539
539
+
switch cm.mode {
475
540
case Normal:
476
541
switch msg.String() {
477
542
case "i", "a":
478
478
-
m.mode = Insert
479
479
-
return m, m.draft.Focus()
543
543
+
cm.mode = Insert
544
544
+
return cm, cm.draft.Focus(), nil
480
545
case "I":
481
481
-
m.mode = Insert
482
482
-
m.draft.CursorStart()
483
483
-
return m, m.draft.Focus()
546
546
+
cm.mode = Insert
547
547
+
cm.draft.CursorStart()
548
548
+
return cm, cm.draft.Focus(), nil
484
549
case "A":
485
485
-
m.mode = Insert
486
486
-
m.draft.CursorEnd()
487
487
-
return m, m.draft.Focus()
488
488
-
case ":":
489
489
-
m.mode = Command
490
490
-
return m, m.prompt.Focus()
550
550
+
cm.mode = Insert
551
551
+
cm.draft.CursorEnd()
552
552
+
return cm, cm.draft.Focus(), nil
491
553
}
492
554
case Insert:
493
555
switch msg.String() {
494
556
case "esc":
495
495
-
m.mode = Normal
496
496
-
m.draft.Blur()
497
497
-
return m, nil
557
557
+
cm.mode = Normal
558
558
+
cm.draft.Blur()
559
559
+
return cm, nil, nil
498
560
case "enter":
499
499
-
if m.sentmsg != nil {
500
500
-
if m.xrpc != nil && m.signeturi != nil {
561
561
+
if cm.sentmsg != nil {
562
562
+
if cm.gsd.xrpc != nil && cm.signeturi != nil {
501
563
var color64 *uint64
502
502
-
if m.color != nil {
503
503
-
c64 := uint64(*m.color)
564
564
+
if cm.gsd.color != nil {
565
565
+
c64 := uint64(*cm.gsd.color)
504
566
color64 = &c64
505
567
}
506
568
lmr := lex.MessageRecord{
507
507
-
SignetURI: *m.signeturi,
508
508
-
Body: *m.sentmsg,
509
509
-
Nick: m.nick,
569
569
+
SignetURI: *cm.signeturi,
570
570
+
Body: *cm.sentmsg,
571
571
+
Nick: cm.gsd.nick,
510
572
Color: color64,
511
573
PostedAt: syntax.DatetimeNow().String(),
512
574
}
513
513
-
m.draft.SetValue("")
514
514
-
m.sentmsg = nil
515
515
-
m.myid = nil
516
516
-
m.signeturi = nil
517
517
-
return m, tea.Batch(sendPub(m.lrcconn), createMSGCmd(m.xrpc, &lmr))
575
575
+
cm.draft.SetValue("")
576
576
+
cm.sentmsg = nil
577
577
+
cm.myid = nil
578
578
+
cm.signeturi = nil
579
579
+
return cm, tea.Batch(sendPub(cm.lrcconn), createMSGCmd(cm.gsd.xrpc, &lmr)), nil
518
580
}
519
519
-
m.draft.SetValue("")
520
520
-
m.sentmsg = nil
521
521
-
return m, sendPub(m.lrcconn)
581
581
+
cm.draft.SetValue("")
582
582
+
cm.sentmsg = nil
583
583
+
return cm, sendPub(cm.lrcconn), nil
522
584
}
523
523
-
return m, nil
524
524
-
}
525
525
-
case Command:
526
526
-
switch msg.String() {
527
527
-
case "esc":
528
528
-
m.mode = Normal
529
529
-
m.prompt.Blur()
530
530
-
m.prompt.SetValue("")
531
531
-
return m, nil
532
532
-
case "enter":
533
533
-
m.mode = Normal
534
534
-
m.prompt.Blur()
535
535
-
v := m.prompt.Value()
536
536
-
m.prompt.SetValue("")
537
537
-
return m, evaluateCommand(v)
538
538
-
default:
585
585
+
return cm, nil, nil
539
586
}
540
587
}
541
588
}
542
542
-
switch m.mode {
589
589
+
switch cm.mode {
543
590
case Normal:
544
544
-
vp, cmd := m.vp.Update(msg)
545
545
-
m.vp = &vp
546
546
-
return m, cmd
547
547
-
case Command:
548
548
-
prompt, cmd := m.prompt.Update(msg)
549
549
-
m.prompt = prompt
550
550
-
return m, cmd
591
591
+
vp, cmd := cm.vp.Update(msg)
592
592
+
cm.vp = vp
593
593
+
return cm, cmd, nil
551
594
case Insert:
552
552
-
draft, cmd := m.draft.Update(msg)
553
553
-
if m.sentmsg == nil && draft.Value() != "" {
595
595
+
draft, cmd := cm.draft.Update(msg)
596
596
+
if cm.sentmsg == nil && draft.Value() != "" {
554
597
nv := draft.Value()
555
555
-
m.sentmsg = &nv
556
556
-
m.draft = &draft
557
557
-
return m, tea.Batch(cmd, sendInsert(m.lrcconn, nv, 0, true))
598
598
+
cm.sentmsg = &nv
599
599
+
cm.draft = draft
600
600
+
return cm, tea.Batch(cmd, sendInsert(cm.lrcconn, nv, 0, true)), nil
558
601
}
559
559
-
if m.sentmsg != nil && *m.sentmsg != draft.Value() {
602
602
+
if cm.sentmsg != nil && *cm.sentmsg != draft.Value() {
560
603
draftutf16 := utf16.Encode([]rune(draft.Value()))
561
561
-
sentutf16 := utf16.Encode([]rune(*m.sentmsg))
604
604
+
sentutf16 := utf16.Encode([]rune(*cm.sentmsg))
562
605
edits := Diff(sentutf16, draftutf16)
563
563
-
m.draft = &draft
606
606
+
cm.draft = draft
564
607
sentmsg := draft.Value()
565
565
-
m.sentmsg = &sentmsg
566
566
-
return m, tea.Batch(cmd, sendEditBatch(m.evtchan, edits))
608
608
+
cm.sentmsg = &sentmsg
609
609
+
return cm, tea.Batch(cmd, sendEditBatch(cm.datachan, edits)), nil
567
610
}
568
568
-
m.draft = &draft
569
569
-
return m, cmd
611
611
+
cm.draft = draft
612
612
+
return cm, cmd, nil
570
613
}
571
571
-
return m, nil
614
614
+
return cm, nil, nil
572
615
}
573
616
574
617
func createMSGCmd(xrpc *PasswordClient, lmr *lex.MessageRecord) tea.Cmd {
···
665
708
}
666
709
}
667
710
668
668
-
func evaluateCommand(command string) tea.Cmd {
711
711
+
func (m model) evaluateCommand(command string) tea.Cmd {
669
712
return func() tea.Msg {
670
713
parts := strings.Split(command, " ")
671
714
if parts == nil {
···
684
727
if len(parts) != 1 {
685
728
return loginMsg{parts[1:]}
686
729
}
730
730
+
case "dial":
731
731
+
if len(parts) != 1 {
732
732
+
return dialMsg{parts[1]}
733
733
+
}
687
734
}
688
735
return nil
689
736
}
737
737
+
}
738
738
+
739
739
+
type dialMsg struct {
740
740
+
value string
690
741
}
691
742
692
743
type loginMsg struct {
···
887
938
func (m model) updateConnectingToChannel(msg tea.Msg) (tea.Model, tea.Cmd) {
888
939
switch msg := msg.(type) {
889
940
case connMsg:
890
890
-
m.state = Connected
891
891
-
m.cancel = msg.cancel
892
892
-
m.msgs = make(map[uint32]*Message)
893
893
-
vp := viewport.New(m.width, m.height-2)
894
894
-
m.vp = &vp
941
941
+
m.gsd.state = Connected
942
942
+
cm := channelmodel{}
943
943
+
cm.wsurl = msg.wsurl
944
944
+
cm.gsd = m.gsd
945
945
+
cm.cancel = msg.cancel
946
946
+
cm.msgs = make(map[uint32]*Message)
947
947
+
vp := viewport.New(m.gsd.width, m.gsd.height-2)
948
948
+
cm.vp = vp
895
949
draft := textinput.New()
896
896
-
draft.Prompt = renderName(m.nick, m.handle) + " "
897
897
-
draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(m.color))
950
950
+
draft.Prompt = renderName(m.gsd.nick, m.gsd.handle) + " "
951
951
+
draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(m.gsd.color))
898
952
draft.Placeholder = "press i to start typing"
899
899
-
draft.Width = m.width - len(draft.Prompt) - 1
900
900
-
m.draft = &draft
901
901
-
go startLRCHandlers(msg.conn, msg.lexconn, m.nick, m.handle, m.color)
902
902
-
m.lrcconn = msg.conn
903
903
-
m.lexconn = msg.lexconn
904
904
-
m.evtchan = make(chan []byte)
905
905
-
go LRCWriter(m.lrcconn, m.evtchan)
953
953
+
draft.Width = m.gsd.width - len(draft.Prompt) - 1
954
954
+
cm.draft = draft
955
955
+
go startLRCHandlers(msg.conn, m.gsd.nick, m.gsd.handle, m.gsd.color)
956
956
+
cm.lrcconn = msg.conn
957
957
+
cm.lexconn = msg.lexconn
958
958
+
cm.datachan = make(chan []byte)
959
959
+
go listenToLexConn(msg.lexconn)
960
960
+
go LRCWriter(cm.lrcconn, cm.datachan)
961
961
+
m.cm = &cm
962
962
+
m.clm = nil
906
963
return m, nil
907
964
}
908
965
return m, nil
909
966
}
910
967
968
968
+
func (m model) updateDialingChannel(msg tea.Msg) (tea.Model, tea.Cmd) {
969
969
+
switch msg := msg.(type) {
970
970
+
case connSimpleMsg:
971
971
+
m.gsd.state = Connected
972
972
+
cm := channelmodel{}
973
973
+
cm.gsd = m.gsd
974
974
+
cm.cancel = msg.cancel
975
975
+
cm.msgs = make(map[uint32]*Message)
976
976
+
vp := viewport.New(m.gsd.width, m.gsd.height-2)
977
977
+
cm.vp = vp
978
978
+
draft := textinput.New()
979
979
+
draft.Prompt = renderName(m.gsd.nick, m.gsd.handle) + " "
980
980
+
draft.PromptStyle = lipgloss.NewStyle().Foreground(ColorFromInt(m.gsd.color))
981
981
+
draft.Placeholder = "press i to start typing"
982
982
+
draft.Width = m.gsd.width - len(draft.Prompt) - 1
983
983
+
cm.draft = draft
984
984
+
go startLRCHandlers(msg.conn, m.gsd.nick, m.gsd.handle, m.gsd.color)
985
985
+
m.cm = &cm
986
986
+
m.clm = nil
987
987
+
}
988
988
+
return m, nil
989
989
+
}
990
990
+
911
991
func LRCWriter(conn *websocket.Conn, datachan chan []byte) {
912
992
for data := range datachan {
913
993
err := conn.WriteMessage(websocket.BinaryMessage, data)
···
941
1021
942
1022
}
943
1023
944
944
-
func startLRCHandlers(conn *websocket.Conn, lexconn *websocket.Conn, nick *string, handle *string, color *uint32) {
1024
1024
+
func startLRCHandlers(conn *websocket.Conn, nick *string, handle *string, color *uint32) {
945
1025
if conn == nil {
946
1026
send(errMsg{errors.New("provided nil conn")})
947
1027
return
···
963
1043
}
964
1044
conn.WriteMessage(websocket.BinaryMessage, data)
965
1045
go listenToConn(conn)
966
966
-
go listenToLexConn(lexconn)
967
1046
}
968
1047
969
1048
type typedJSON struct {
···
1030
1109
func (m model) updateResolvingChannel(msg tea.Msg) (tea.Model, tea.Cmd) {
1031
1110
switch msg := msg.(type) {
1032
1111
case resolutionMsg:
1033
1033
-
wsurl := fmt.Sprintf("%s%s", m.curchannel.Host, msg.resolution.URL)
1034
1034
-
m.wsurl = &wsurl
1035
1035
-
m.state = ConnectingToChannel
1112
1112
+
c := m.clm.curchannel()
1113
1113
+
var host string
1114
1114
+
if c != nil {
1115
1115
+
host = c.Host
1116
1116
+
}
1117
1117
+
wsurl := fmt.Sprintf("%s%s", host, msg.resolution.URL)
1118
1118
+
m.gsd.state = ConnectingToChannel
1036
1119
ctx, cancel := context.WithCancel(context.Background())
1037
1037
-
return m, m.connectToChannel(ctx, cancel)
1120
1120
+
return m, m.connectToChannel(ctx, cancel, wsurl)
1038
1121
}
1039
1122
return m, nil
1040
1123
}
1041
1124
1042
1042
-
func (m model) connectToChannel(ctx context.Context, cancel func()) tea.Cmd {
1125
1125
+
func (m model) dialingChannel(url string) tea.Cmd {
1043
1126
return func() tea.Msg {
1044
1127
dialer := websocket.DefaultDialer
1045
1128
dialer.Subprotocols = []string{"lrc.v1"}
1046
1046
-
if m.wsurl == nil {
1047
1047
-
return errMsg{errors.New("nil wsurl!")}
1129
1129
+
ctx, cancel := context.WithCancel(context.Background())
1130
1130
+
conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://%s", url), http.Header{})
1131
1131
+
if err != nil {
1132
1132
+
cancel()
1133
1133
+
return errMsg{err}
1048
1134
}
1049
1049
-
conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://%s", *m.wsurl), http.Header{})
1135
1135
+
return connSimpleMsg{conn, cancel}
1136
1136
+
}
1137
1137
+
}
1138
1138
+
1139
1139
+
type connSimpleMsg struct {
1140
1140
+
conn *websocket.Conn
1141
1141
+
cancel func()
1142
1142
+
}
1143
1143
+
1144
1144
+
func (m model) connectToChannel(ctx context.Context, cancel func(), wsurl string) tea.Cmd {
1145
1145
+
return func() tea.Msg {
1146
1146
+
dialer := websocket.DefaultDialer
1147
1147
+
dialer.Subprotocols = []string{"lrc.v1"}
1148
1148
+
conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://%s", wsurl), http.Header{})
1050
1149
if err != nil {
1051
1150
return errMsg{err}
1052
1151
}
1053
1152
1054
1153
dialer = websocket.DefaultDialer
1055
1055
-
lexconn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://xcvr.org/xrpc/org.xcvr.lrc.subscribeLexStream?uri=%s", m.curchannel.URI), http.Header{})
1154
1154
+
c := m.clm.curchannel()
1155
1155
+
var uri string
1156
1156
+
if c != nil {
1157
1157
+
uri = c.URI
1158
1158
+
}
1159
1159
+
lexconn, _, err := dialer.DialContext(ctx, fmt.Sprintf("wss://xcvr.org/xrpc/org.xcvr.lrc.subscribeLexStream?uri=%s", uri), http.Header{})
1056
1160
if err != nil {
1057
1161
return errMsg{err}
1058
1162
}
1059
1059
-
return connMsg{conn, lexconn, cancel}
1163
1163
+
return connMsg{conn, lexconn, cancel, wsurl}
1060
1164
}
1061
1165
}
1062
1166
···
1064
1168
conn *websocket.Conn
1065
1169
lexconn *websocket.Conn
1066
1170
cancel func()
1171
1171
+
wsurl string
1067
1172
}
1068
1173
1069
1174
const (
···
1074
1179
func (m model) updateGettingChannels(msg tea.Msg) (tea.Model, tea.Cmd) {
1075
1180
switch msg := msg.(type) {
1076
1181
case channelsMsg:
1182
1182
+
clm := channellistmodel{}
1077
1183
items := make([]list.Item, 0, len(msg.channels))
1078
1184
for _, channel := range msg.channels {
1079
1185
items = append(items, ChannelItem{channel})
1080
1186
}
1081
1081
-
list := list.New(items, ChannelItemDelegate{}, m.width, m.height)
1187
1187
+
list := list.New(items, ChannelItemDelegate{}, m.gsd.width, m.gsd.height-1)
1082
1188
list.Styles = defaultStyles()
1083
1189
list.Title = "org.xcvr.feed.getChannels"
1084
1084
-
m.list = &list
1085
1085
-
m.state = ChannelList
1190
1190
+
clm.list = list
1191
1191
+
m.gsd.state = ChannelList
1192
1192
+
clm.gsd = m.gsd
1193
1193
+
m.clm = &clm
1086
1194
return m, nil
1087
1195
}
1088
1196
return m, nil
1089
1197
}
1090
1198
1091
1091
-
func (m model) updateChannelList(msg tea.Msg) (tea.Model, tea.Cmd) {
1092
1092
-
if m.list == nil {
1093
1093
-
err := errors.New("no list!")
1094
1094
-
m.error = &err
1095
1095
-
m.state = Error
1096
1096
-
return m, nil
1199
1199
+
func (clm channellistmodel) curchannel() *Channel {
1200
1200
+
switch i := clm.list.SelectedItem().(type) {
1201
1201
+
case ChannelItem:
1202
1202
+
return &i.channel
1097
1203
}
1204
1204
+
return nil
1205
1205
+
}
1206
1206
+
1207
1207
+
func (clm channellistmodel) updateChannelList(msg tea.Msg) (channellistmodel, tea.Cmd, error) {
1098
1208
switch msg := msg.(type) {
1099
1209
case tea.KeyMsg:
1100
1210
switch msg.String() {
1101
1211
case "enter":
1102
1102
-
m.state = ResolvingChannel
1103
1103
-
i, ok := m.list.SelectedItem().(ChannelItem)
1104
1104
-
if ok {
1105
1105
-
uri := i.URI()
1212
1212
+
if clm.list.FilterState() == list.Filtering {
1213
1213
+
break
1214
1214
+
}
1215
1215
+
clm.gsd.state = ResolvingChannel
1216
1216
+
cc := clm.curchannel()
1217
1217
+
if cc != nil {
1218
1218
+
uri := cc.URI
1106
1219
did, _ := DidFromUri(uri)
1107
1220
rkey, err := RkeyFromUri(uri)
1108
1221
if err != nil {
1109
1109
-
m.error = &err
1110
1110
-
m.state = Error
1111
1111
-
return m, nil
1222
1222
+
return clm, nil, err
1112
1223
}
1113
1113
-
m.curchannel = &i.channel
1114
1114
-
m.list = nil
1115
1115
-
m.channels = nil
1116
1116
-
return m, ResolveChannel(i.Host(), did, rkey)
1224
1224
+
return clm, ResolveChannel(cc.Host, did, rkey), nil
1117
1225
} else {
1118
1226
err := errors.New("bad list type")
1119
1119
-
m.error = &err
1120
1120
-
m.state = Error
1121
1121
-
return m, nil
1227
1227
+
return clm, nil, err
1122
1228
}
1123
1229
}
1124
1230
}
1125
1125
-
list, cmd := m.list.Update(msg)
1126
1126
-
m.list = &list
1127
1127
-
return m, cmd
1231
1231
+
list, cmd := clm.list.Update(msg)
1232
1232
+
clm.list = list
1233
1233
+
return clm, cmd, nil
1128
1234
}
1129
1235
1130
1236
func ResolveChannel(host string, did string, rkey string) tea.Cmd {
···
1158
1264
}
1159
1265
1160
1266
func (m model) View() string {
1161
1161
-
switch m.state {
1267
1267
+
var pv string
1268
1268
+
if m.cmding {
1269
1269
+
pv = m.prompt.View()
1270
1270
+
}
1271
1271
+
switch m.gsd.state {
1162
1272
case Splash:
1163
1273
return m.splashView()
1164
1274
case GettingChannels:
···
1169
1279
}
1170
1280
return "broke so bad there isn't an error"
1171
1281
case ChannelList:
1172
1172
-
return m.channelListView()
1282
1282
+
return m.clm.channelListView(m.cmding, pv)
1173
1283
case ResolvingChannel:
1174
1284
return "resolving channel"
1175
1285
case ConnectingToChannel:
1176
1286
return m.connectingView()
1177
1287
case Connected:
1178
1178
-
return m.connectedView()
1288
1288
+
return m.cm.connectedView(m.cmding, pv)
1179
1289
default:
1180
1290
return "under construction"
1181
1291
}
1182
1292
}
1183
1293
1184
1184
-
func (m model) connectedView() string {
1185
1185
-
var vpt string
1186
1186
-
if m.vp != nil {
1187
1187
-
vpt = m.vp.View()
1188
1188
-
}
1189
1189
-
address := "lrc://"
1190
1190
-
if m.wsurl != nil {
1191
1191
-
address = fmt.Sprintf("%s%s", address, *m.wsurl)
1192
1192
-
}
1193
1193
-
var topic string
1194
1194
-
if m.topic != nil {
1195
1195
-
topic = *m.topic
1196
1196
-
}
1197
1197
-
remainingspace := m.width - len(address) - len(topic)
1198
1198
-
var footertext string
1199
1199
-
if m.mode == Command {
1200
1200
-
footertext = m.prompt.View()
1201
1201
-
} else if remainingspace < 1 {
1202
1202
-
addressremaining := m.width - len(address)
1203
1203
-
if addressremaining < 0 {
1204
1204
-
footertext = strings.Repeat(" ", m.width)
1294
1294
+
func (cm channelmodel) connectedView(cmding bool, prompt string) string {
1295
1295
+
vpt := cm.vp.View()
1296
1296
+
var footer string
1297
1297
+
if cmding {
1298
1298
+
footer = prompt
1299
1299
+
} else {
1300
1300
+
address := "lrc://"
1301
1301
+
address = fmt.Sprintf("%s%s", address, cm.wsurl)
1302
1302
+
var topic string
1303
1303
+
if cm.topic != nil {
1304
1304
+
topic = *cm.topic
1305
1305
+
}
1306
1306
+
remainingspace := cm.gsd.width - len(address) - len(topic)
1307
1307
+
var footertext string
1308
1308
+
if remainingspace < 1 {
1309
1309
+
addressremaining := cm.gsd.width - len(address)
1310
1310
+
if addressremaining < 0 {
1311
1311
+
footertext = strings.Repeat(" ", cm.gsd.width)
1312
1312
+
} else {
1313
1313
+
footertext = fmt.Sprintf("%s%s", address, strings.Repeat(" ", cm.gsd.width-len(address)))
1314
1314
+
}
1205
1315
} else {
1206
1206
-
footertext = fmt.Sprintf("%s%s", address, strings.Repeat(" ", m.width-len(address)))
1316
1316
+
footertext = fmt.Sprintf("%s%s%s", address, strings.Repeat(" ", remainingspace), topic)
1207
1317
}
1208
1208
-
} else {
1209
1209
-
footertext = fmt.Sprintf("%s%s%s", address, strings.Repeat(" ", remainingspace), topic)
1210
1210
-
}
1211
1211
-
insert := m.mode == Insert
1212
1212
-
footerstyle := lipgloss.NewStyle().Reverse(insert)
1213
1213
-
if m.mode != Command {
1214
1214
-
footerstyle = footerstyle.Foreground(ColorFromInt(m.color))
1215
1215
-
}
1216
1216
-
footer := footerstyle.Render(footertext)
1217
1217
-
var draftText string
1218
1218
-
if m.draft != nil {
1219
1219
-
draftText = m.draft.View()
1318
1318
+
insert := cm.mode == Insert
1319
1319
+
footerstyle := lipgloss.NewStyle().Reverse(insert)
1320
1320
+
footerstyle = footerstyle.Foreground(ColorFromInt(cm.gsd.color))
1321
1321
+
footer = footerstyle.Render(footertext)
1220
1322
}
1323
1323
+
draftText := cm.draft.View()
1221
1324
return fmt.Sprintf("%s\n%s\n%s", vpt, draftText, footer)
1222
1325
}
1223
1326
1224
1327
func (m model) connectingView() string {
1225
1225
-
blip := m.wsurl
1226
1226
-
if blip == nil {
1227
1227
-
return "resolving channel\nSOMETHING WENT HORRIBLY WRONG"
1228
1228
-
}
1229
1229
-
return fmt.Sprintf("resolving channel\nconnecting to %s", *m.wsurl)
1328
1328
+
return "resolving channel\nconnecting to channel"
1230
1329
}
1231
1330
1232
1232
-
func (m model) channelListView() string {
1233
1233
-
return m.list.View()
1331
1331
+
func (clm channellistmodel) channelListView(cmding bool, prompt string) string {
1332
1332
+
lv := clm.list.View()
1333
1333
+
cv := ""
1334
1334
+
if cmding {
1335
1335
+
cv = prompt
1336
1336
+
}
1337
1337
+
return fmt.Sprintf("%s\n%s", lv, cv)
1234
1338
}
1235
1339
1236
1340
func (m model) splashView() string {
···
1262
1366
to start!
1263
1367
`
1264
1368
s := fmt.Sprintf("\n\n\n\n%s%s%s%s%s%s%s%s%s%s%s%s%s", style.Render(part00), style.Render(part01), style.Render(part02), style.Render(part03), style.Render(part1), text1, style.Render(part2), style.Render(part25), text2, style.Render(part3), text3, style.Render(part4), text4)
1265
1265
-
offset := lipgloss.NewStyle().MarginLeft((m.width - 58) / 2)
1369
1369
+
offset := lipgloss.NewStyle().MarginLeft((m.gsd.width - 58) / 2)
1266
1370
return offset.Render(s)
1267
1371
}
1268
1372