-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheditor.asm
More file actions
3272 lines (3013 loc) · 94.9 KB
/
editor.asm
File metadata and controls
3272 lines (3013 loc) · 94.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;
; PETPROJECT v0.1 — Editor
;
.setcpu "6502"
; ============================================================================
; Kernal routines
; ============================================================================
GETIN = $FFE4
SETLFS = $FFBA
SETNAM = $FFBD
OPEN = $FFC0
CLOSE = $FFC3
CHKIN = $FFC6
CHKOUT = $FFC9
CLRCHN = $FFCC
CHRIN = $FFCF
CHROUT = $FFD2
LOAD = $FFD5
SAVE = $FFD8
READST = $FFB7 ; read I/O status word into A
; Kernal zero-page variables
FA = $BA ; current device number (kernal ZP)
; ============================================================================
; System locations
; ============================================================================
JIFFY_LO = $A2 ; jiffy counter low byte (updated by IRQ)
RPTFLG = $028A ; KERNAL key-repeat control: $80 = all keys
; repeat, $40 = none, $00 = cursor/space/del
; only (the power-on default)
RPTFLG_ALL = $80 ; value that makes every key auto-repeat
NMI_VEC = $0318 ; NMI indirect vector (lo)
NMI_VEC_HI = $0319 ; NMI indirect vector (hi)
; Warm-boot communication block. Lives at $0200 (start of page 2).
; On cold boot this area is uninitialised. On IDE-triggered warm boot
; (app finished / NMI stub fired) the stub writes WARM_MAGIC here before
; reloading us, so start: can tell the difference.
;
; Future: when config-file support is added, load_settings will read
; petproject.cfg from disk and overlay these defaults at boot time.
; Nothing else in the boot path needs to change.
BOOT_MAGIC = $0200 ; 1 byte: $42 = warm boot, anything else = cold
BOOT_FNAME_LEN = $0201 ; 1 byte: length of source filename to reload
BOOT_FNAME = $0202 ; 16 bytes: source filename ($0202–$0211)
WARM_MAGIC = $42 ; sentinel value
RESUME_MAGIC = $52 ; script runner finished: re-enter, keep buffer intact
; ============================================================================
; Screen / VIC constants
; ============================================================================
SCREEN = $0400
COLOR = $D800
COLS = 40
TOTAL_ROWS = 25
CONTENT_ROWS = 24 ; total rows minus the status row
STATUS_ROW = SCREEN ; row 0
CONTENT_TOP = SCREEN + COLS ; row 1, where the document starts
VIC_BORDER = $D020
VIC_BG = $D021
; ============================================================================
; PETSCII / key constants
; ============================================================================
PET_CR = $0D
PET_STOP = $03 ; RUN/STOP key PETSCII value
PET_SPACE = $20 ; PETSCII space
PET_0 = $30 ; PETSCII '0'
PET_9 = $39 ; PETSCII '9'
SCR_SPACE = $20
KEY_F1 = $85 ; PETSCII for F1 key (settings)
KEY_F2 = $89 ; PETSCII for F2 key (SHIFT+F1 — page up)
KEY_F3 = $86 ; PETSCII for F3 key (load)
KEY_F4 = $8A ; PETSCII for F4 key (SHIFT+F3 — page down)
KEY_F5 = $87 ; PETSCII for F5 key (save)
KEY_F6 = $8B ; PETSCII for F6 key (SHIFT+F5 — consumed, no action)
KEY_F7 = $88 ; PETSCII for F7 key (quit)
KEY_F8 = $8C ; PETSCII for F8 key (SHIFT+F7 — module picker)
KEY_CRSR_RT = $1D
KEY_CRSR_LT = $9D
KEY_CRSR_DN = $11
KEY_CRSR_UP = $91
KEY_CTRL_F = $06 ; CTRL+F — search / replace (loads MODSFR)
KEY_CTRL_N = $0E ; CTRL+N — new file
KEY_CTRL_L = $0C ; CTRL+L — force full screen redraw
KEY_CTRL_R = $12 ; CTRL+R - Run a script
; ============================================================================
; Default color values
; Stored in SETTING_BORDER / SETTING_BG / SETTING_FG at runtime so the
; user can change them from the settings popover without recompiling.
; ============================================================================
DEFAULT_FG_COLOR = $0E ; light blue
DEFAULT_STATUS_COLOR = $01 ; white (status row — not user-adjustable yet)
DEFAULT_BORDER_COLOR = $06 ; blue
DEFAULT_BG_COLOR = $00 ; black
; ---- Modal prompt ("attention") bar ------------------------------------
; A modal prompt repaints the whole status row as a reverse-video bar so it
; is impossible to mistake for the ambient (idle) status line. Reverse video
; (screen-code bit 7 set) renders each cell as a solid block in the color-RAM
; color with the glyph knocked out in the screen background color — i.e. a
; bright bar with dark lettering. modal_exit calls render_status to restore.
MODAL_BAR_COLOR = $07 ; yellow — the alert bar color
SCR_REVERSE = $80 ; OR into a screen code for reverse video
BUF_SIZE = $6000 ; 24 K working buffer
; ============================================================================
; Zero-page allocations
; ============================================================================
.zeropage
GAP_START: .res 2
GAP_END: .res 2
BUF_PTR: .res 2
SCREEN_PTR: .res 2
TOP_LINE: .res 2
LEFT_COL: .res 1
TMP: .res 2
SAVED_X: .res 1 ; lookup_screen uses this to preserve X
CURSOR_ROW: .res 1 ; visible row 0..23 in content area
CURSOR_COL: .res 1 ; visible column 0..39 (after LEFT_COL applied)
COL_SAVE: .res 1 ; saved column for up/down movement
WORK_PTR: .res 2 ; scratch pointer for cursor walks
LPTR: .res 2 ; load/save label pointer (loadsave.asm)
CLR_PTR: .res 2 ; color RAM row pointer
CLR_KWLEN: .res 1 ; keyword length returned by col_try_keyword
CLR_CTMP: .res 2 ; colortab walk pointer (col_try_keyword internal)
; col_try_keyword uses ZP scratch at $3A for the matched token byte.
; This byte is above the editor's reserved ZP block ($02-$1B) and is
; the same address used by modtok.asm — safe to alias here since the
; editor never calls tokenizer code directly.
KW_TOKEN = $3A ; token byte from col_try_keyword (colorize scratch)
.segment "LOADADDR"
.export __LOADADDR__
__LOADADDR__:
.word $0801
; ============================================================================
; BASIC stub: 10 SYS 2061 (jumps to $080D)
; ============================================================================
.segment "STARTUP"
; BASIC stub: 10 SYS 2061 (12 bytes at $0801, jumps to $080D = 2061)
; NOTE: uses non-local label _basic_end instead of @next.
; ca65 local labels (@foo) are scoped between non-local labels; with no
; non-local label preceding this segment, @next silently resolved to $0000,
; corrupting the next-line link and shifting start: 2 bytes forward so
; SYS 2061 landed on a BRK instead of the entry point.
.word _basic_end ; pointer to next BASIC line (= $080B)
.word 10 ; line number
.byte $9E ; SYS token
.byte "2061" ; argument: start: is at $080D = 2061 decimal
.byte 0 ; end-of-line
_basic_end:
.word 0 ; end-of-program
; ============================================================================
; Entry point — cold vs warm boot
; ============================================================================
.code
start:
; Verify the BASIC stub above is exactly 12 bytes so SYS 2061 lands here.
; If this assertion fires, the stub changed size — update "2061" to match.
.assert * = $080D, error, "BASIC SYS address mismatch: start: must be at $080D"
; The editor never returns to BASIC's SYS handler. Reset the stack now
; to discard BASIC's call frames so OPEN/CHKIN frames have room to run.
ldx #$FF
txs
jsr build_screen_lookup
; Enable auto-repeat on ALL keys. The KERNAL default ($00) repeats only the
; cursor keys, space, and INST/DEL, so a held letter would emit just one
; character. An editor wants every key to repeat while held.
lda #RPTFLG_ALL
sta RPTFLG
jsr load_settings
; Check for warm-boot magic written by the NMI/exit stub.
lda BOOT_MAGIC
cmp #RESUME_MAGIC
beq @resume ; script runner finished — buffer already in RAM
cmp #WARM_MAGIC
beq warm_start
jmp cold_start ; neither magic — cold start
@resume:
; MODSCRH's hnd_end_script restored the full IDE image (including the gap
; buffer the user was editing) from the REU before jumping here, and reset
; the stack above. Settings are still live in RAM. So we skip all init and
; just repaint + resume the input loop, preserving the buffer exactly.
lda #0
sta BOOT_MAGIC ; consume the magic so next boot is cold
jmp editor_ready
; ------------------------------------------------------------------
; Cold start — blank buffer, default settings
; ------------------------------------------------------------------
cold_start:
jsr init_settings ; write defaults into SETTING_* bytes
jsr setup_screen ; clear display, init gap buffer
jmp editor_ready
; ------------------------------------------------------------------
; Warm start — IDE was reloaded after running user's program.
; Restore the source file the user was editing.
; ------------------------------------------------------------------
warm_start:
lda #0
sta BOOT_MAGIC ; consume the magic so next boot is cold
jsr init_settings ; apply defaults (config file would overlay
; these in load_settings once implemented)
jsr setup_screen_blank ; clear display, empty gap buffer
; Future: if BOOT_FNAME_LEN > 0, call load_file with BOOT_FNAME/LEN.
; For now, warm boot just opens a blank buffer.
lda BOOT_FNAME_LEN
beq editor_ready ; no filename recorded — start blank
; jsr load_source_file ; (file I/O not yet implemented)
editor_ready:
jsr apply_colors ; push SETTING_* values to VIC + color RAM
jsr render_status
jsr ensure_cursor_visible
jsr render_viewport
jsr draw_cursor
; ============================================================================
; Main input loop
; ============================================================================
main_loop:
jsr GETIN
bne @has_key
jmp @no_key
@has_key:
; ---- Function keys ----
; Physical keys: F1=settings, F3=load, F5=save, F7=quit.
; Shifted variants (F2/F4/F6/F8) are consumed silently to prevent
; them falling through to do_insert and corrupting the buffer.
cmp #KEY_F1
bne @try_f2
jmp @do_settings ; F1 = settings
@try_f2:
cmp #KEY_F2
bne @try_f3
jmp @do_page_up ; SHIFT+F1 — page up
@try_f3:
cmp #KEY_F3
bne @try_f4
jmp @do_load ; F3 = load
@try_f4:
cmp #KEY_F4
bne @try_f5
jmp @do_page_down ; SHIFT+F3 — page down
@try_f5:
cmp #KEY_F5
bne @try_f6
jmp @do_save ; F5 = save
@try_f6:
cmp #KEY_F6
bne @try_f7
jmp @do_load_source ; F6 (SHIFT+F5) = load raw source (SEQ) file
@try_f7:
cmp #KEY_F7
bne @try_f8
jmp @quit ; F7 = quit
@try_f8:
cmp #KEY_F8
bne @try_crsr_rt
jmp @do_modules ; F8 (SHIFT+F7) — module picker
@try_crsr_rt:
; ---- CTRL+F — search / replace ----
cmp #KEY_CTRL_F
bne @try_ctrl_n
jmp @do_search
@try_ctrl_n:
; ---- CTRL+N — new file ----
cmp #KEY_CTRL_N
bne @try_crsr_rt2
jmp @do_new
@try_crsr_rt2:
; ---- Cursor movement ----
cmp #KEY_CRSR_RT
bne @try_lt
jsr move_begin
jsr cursor_right
jmp @moved
@try_lt:
cmp #KEY_CRSR_LT
bne @try_dn
jsr move_begin
jsr cursor_left
jmp @moved
@try_dn:
cmp #KEY_CRSR_DN
bne @try_up
jsr move_begin
jsr cursor_down
jmp @moved
@try_up:
cmp #KEY_CRSR_UP
bne @try_return
jsr move_begin
jsr cursor_up
jmp @moved
; ---- Text input ----
@try_return:
cmp #$0D ; RETURN — insert newline
bne @try_del
jsr insert_return
lda #$FF
sta IS_DIRTY
jmp @moved_render
@try_del:
cmp #$14 ; INST/DEL — backspace
bne @try_ctrl_l
jsr do_backspace
lda #$FF
sta IS_DIRTY
jmp @moved_render
@try_ctrl_l:
cmp #KEY_CTRL_L ; CTRL+L — force full screen redraw
bne @try_ctrl_r
jsr apply_colors
jsr render_status
jsr render_viewport
jsr draw_cursor
jmp main_loop
@try_ctrl_r:
cmp #KEY_CTRL_R
bne @try_insert
jsr do_run_script
jmp main_loop
@try_insert:
cmp #$80 ; reject PETSCII >= $80 (token bytes)
bcs @unhandled
cmp #$20 ; reject control chars below $20
bcc @unhandled
; ---- Printable insert fast path ----
; A printable char only changes the current line. Snapshot the viewport
; origin, insert, reposition the cursor, and if the viewport did NOT scroll
; (TOP_LINE and LEFT_COL unchanged) repaint just the cursor's row instead
; of all 24. Falls back to a full redraw when a scroll was triggered.
ldx TOP_LINE
stx FP_TOP_SAVE
ldx TOP_LINE+1
stx FP_TOP_SAVE+1
ldx LEFT_COL
stx FP_LEFT_SAVE
jsr do_insert ; A still holds the key
ldx #$FF
stx IS_DIRTY
jsr ensure_cursor_visible
; Did the viewport scroll? Compare saved origin to current.
lda TOP_LINE
cmp FP_TOP_SAVE
bne @moved_full
lda TOP_LINE+1
cmp FP_TOP_SAVE+1
bne @moved_full
lda LEFT_COL
cmp FP_LEFT_SAVE
bne @moved_full
; No scroll — repaint only the cursor's row.
jsr render_cursor_row
jsr draw_cursor
jmp main_loop
@moved_full:
jsr render_viewport
jsr draw_cursor
jmp main_loop
@unhandled:
jmp main_loop
@moved:
; Cursor-movement fast path. A pure cursor move changes nothing on screen
; except which cell is highlighted, so when the viewport does not scroll we
; skip the full 24-row render+recolor entirely: the old cursor cell was
; already cleared by move_begin, the content rows are still correct, so we
; only need to draw the cursor at its new position. This is what made
; arrow-key movement slow (especially in asm mode, where the per-row
; colorizer cost is higher) — every keypress was repainting the whole screen.
jsr ensure_cursor_visible
lda TOP_LINE
cmp FP_TOP_SAVE
bne @cmoved_full
lda TOP_LINE+1
cmp FP_TOP_SAVE+1
bne @cmoved_full
lda LEFT_COL
cmp FP_LEFT_SAVE
bne @cmoved_full
; No scroll — just draw the cursor at the new cell. No re-render.
jsr draw_cursor
jmp main_loop
@cmoved_full:
; Viewport scrolled — full redraw (this also clears the old cursor cell).
jsr render_viewport
jsr draw_cursor
jmp main_loop
; Unconditional full-render path for content-changing or always-scrolling keys
; (RETURN, INST/DEL, page up/down). These don't go through move_begin, so the
; scroll-detection snapshot isn't valid for them; they always repaint fully.
@moved_render:
jsr ensure_cursor_visible
jsr render_viewport
jsr draw_cursor
jmp main_loop
@no_key:
; Blink only if SETTING_BLINK is non-zero.
lda SETTING_BLINK
beq @blink_off_always
lda JIFFY_LO
and #$10 ; ~2 Hz blink period
beq @cursor_off
jsr draw_cursor
jmp main_loop
@cursor_off:
jsr erase_cursor
jmp main_loop
@blink_off_always:
jsr draw_cursor ; blink disabled — cursor always on
jmp main_loop
; ---- Function key handlers (stubs until feature implemented) ----
@do_settings:
jsr do_settings_popover
jmp main_loop
@do_load:
jsr do_load_file
jmp main_loop
@do_load_source:
jsr do_load_source_file
jmp main_loop
@do_save:
jsr do_save_file
jmp main_loop
@do_modules:
jsr do_modules_popup
jmp main_loop
@do_search:
jsr do_search_replace
jmp main_loop
@do_new:
jsr do_new_file
jmp main_loop
@do_page_up:
jsr page_up
jmp @moved_render
@do_page_down:
jsr page_down
jmp @moved_render
@quit:
@check_is_dirty:
; If buffer is dirty, prompt "SAVE BEFORE QUIT (Y/N)?"
lda IS_DIRTY
beq @quit_now
jsr quit_dirty_prompt ; C=1 → cancelled (don't quit)
bcc @quit_now
jmp main_loop ; user cancelled — back to editing
@quit_now:
; Clear screen before handing back to BASIC.
lda #$93 ; PETSCII clear-screen character
jsr $FFD2 ; CHROUT
; Hand back to BASIC via its COLD-start entry ($E394), NOT warm start.
;
; At boot we did `ldx #$FF / txs`, discarding BASIC's call stack, and the
; editor has overwritten zero page (gap pointers, $3A, etc.) and page 2
; throughout the session. BASIC's warm start ($E37B) assumes its stack and
; ZP pointers are still valid, so resuming through it left BASIC with a
; corrupted expression stack — the next evaluation (e.g. LOAD"$",8) failed
; with ?FORMULA TOO COMPLEX. Cold start runs JSR $E3BF (re-init BASIC RAM:
; rebuilds the stack, ZP pointers, and vectors), giving a clean machine.
;
; Trade-off: this resets BASIC fully (banner shown, any BASIC program in
; memory is cleared). That is the correct, safe contract for a SYS-launched
; tool that took over the machine.
jmp $E394 ; BASIC cold start (per ($A000) in this ROM)
; ============================================================================
; move_begin — preamble shared by all cursor-movement keys.
; Snapshots the viewport origin (for scroll detection in the @moved fast path)
; and erases the cursor at its CURRENT cell BEFORE the move routine changes
; CURSOR_ROW/COL, so the no-scroll fast path leaves no stale highlight behind.
; Clobbers A, X, Y (erase_cursor), WORK_PTR.
; ============================================================================
move_begin:
lda TOP_LINE
sta FP_TOP_SAVE
lda TOP_LINE+1
sta FP_TOP_SAVE+1
lda LEFT_COL
sta FP_LEFT_SAVE
jmp erase_cursor ; tail-call: erases old cell, then rts
; ============================================================================
; do_new_file — CTRL+N handler: clear buffer, optionally saving first.
;
; If dirty: prompt "SAVE BEFORE NEW (Y/N)?". Y=save then new, N=new without
; saving, STOP=cancel.
; Resets gap buffer, cursor, viewport, IS_DIRTY, IS_BASIC, IS_NEW_FILE,
; and FNAME_LEN so the save prompt starts blank next time.
; ============================================================================
do_new_file:
lda IS_DIRTY
beq @clear
; Prompt — reverse-video alert bar so it can't be mistaken for idle status.
lda #<new_dirty_text
ldx #>new_dirty_text
jsr modal_draw_text
@wait:
jsr GETIN
beq @wait
cmp #PET_STOP
beq @cancel
cmp #'Y'
beq @save
cmp #'y'
beq @save
cmp #'N'
beq @clear
cmp #'n'
beq @clear
jmp @wait
@save:
lda #<work_buf_end
sta GAP_END
lda #>work_buf_end
sta GAP_END+1
jsr do_save_file ; if save is cancelled, still proceed to clear
@clear:
; Reset gap buffer to empty
lda #<work_buf
sta GAP_START
lda #>work_buf
sta GAP_START+1
lda #<work_buf_end
sta GAP_END
lda #>work_buf_end
sta GAP_END+1
; Reset viewport and cursor
lda #<work_buf
sta TOP_LINE
lda #>work_buf
sta TOP_LINE+1
lda #0
sta LEFT_COL
sta CURSOR_ROW
sta CURSOR_COL
; Reset file state
lda #0
sta IS_DIRTY
sta FNAME_LEN ; blank filename for next save prompt
lda #$FF
sta IS_NEW_FILE
sta IS_BASIC
jsr ensure_cursor_visible
jsr render_status
jsr render_viewport
jsr draw_cursor
@cancel:
jsr modal_exit ; STOP: restore idle status bar before returning
rts
new_dirty_text:
; "SAVE BEFORE NEW (Y/N)?" in screen codes
.byte $13,$01,$16,$05,$20,$02,$05,$06,$0F,$12,$05,$20,$0E,$05,$17
.byte $20,$28,$19,$2F,$0E,$29,$3F, 0
; ============================================================================
; quit_dirty_prompt — "SAVE BEFORE QUIT (Y/N)?" on status bar.
;
; If Y: calls do_save_file, then returns C=0 (proceed to quit).
; If N: returns C=0 (quit without saving).
; If STOP: returns C=1 (cancel quit, back to editor).
; Clobbers: A, X, Y.
; ============================================================================
quit_dirty_prompt:
lda #<quit_dirty_text
ldx #>quit_dirty_text
jsr modal_draw_text
@wait:
jsr GETIN
beq @wait
cmp #PET_STOP
beq @cancel
cmp #'Y'
beq @save
cmp #'y'
beq @save
cmp #'N'
beq @nosave
cmp #'n'
beq @nosave
jmp @wait
@save:
jsr do_save_file ; save — clears IS_DIRTY on success
clc
rts
@nosave:
clc
rts
@cancel:
jsr modal_exit ; restore idle status bar (caller returns to editor)
sec
rts
quit_dirty_text:
; "SAVE BEFORE QUIT (Y/N)?" in screen codes
.byte $13,$01,$16,$05,$20,$02,$05,$06,$0F,$12,$05,$20,$11,$15,$09,$14
.byte $20,$28,$19,$2F,$0E,$29,$3F, 0
; ============================================================================
; init_settings — write compiled-in defaults into the SETTING_* BSS bytes.
; Called on both cold and warm start before load_settings overlays from disk.
; ============================================================================
init_settings:
lda #DEFAULT_BORDER_COLOR
sta SETTING_BORDER
lda #DEFAULT_BG_COLOR
sta SETTING_BG
lda #DEFAULT_FG_COLOR
sta SETTING_FG
lda #1 ; cursor blink on by default
sta SETTING_BLINK
lda #8 ; default drive = 8
sta SETTING_DRIVE
lda #0
sta FNAME_LEN
lda #0
sta IS_BASIC
lda #$FF
sta IS_NEW_FILE ; new editor session starts with a new file
lda #0
sta IS_DIRTY
rts
; ============================================================================
; load_settings — future: read petproject.cfg from disk and populate
; SETTING_* bytes. Currently a no-op stub.
;
; Implementation notes for when this is wired up:
; - Use SETTING_DRIVE to open the file (drive may have been changed last
; session, but we don't know it yet — always load cfg from drive 8).
; - File format TBD; simplest is fixed-offset binary record matching the
; SETTING_* layout so it can be read with a single sequential read.
; - If file not found (ST=$42 after OPEN), silently return — defaults stand.
; - Call apply_colors after returning so any stored colors take effect.
; ============================================================================
load_settings:
rts ; stub — remove this rts when implemented
; ============================================================================
; save_settings — future: write SETTING_* bytes to petproject.cfg on disk.
; Currently a no-op stub.
;
; Implementation notes:
; - Always save to drive 8 (so cfg is findable on cold boot before we
; know the user's preferred drive).
; - Write SETTING_DRIVE, SETTING_BORDER, SETTING_BG, SETTING_FG,
; SETTING_BLINK as a fixed binary record (5 bytes).
; - Call from settings popover on close (only if settings changed).
; ============================================================================
save_settings:
rts ; stub — remove this rts when implemented
; ============================================================================
; apply_colors — push SETTING_BORDER / SETTING_BG / SETTING_FG to hardware.
; Call after changing any color setting to make it take effect immediately.
; ============================================================================
apply_colors:
lda SETTING_BORDER
sta VIC_BORDER
lda SETTING_BG
sta VIC_BG
; Repaint color RAM with SETTING_FG (leaves status row alone).
; We write all 1000 bytes; the status row gets overwritten but
; render_status repaints it white afterward.
lda SETTING_FG
ldx #0
@color_loop:
sta COLOR+$000,x
sta COLOR+$100,x
sta COLOR+$200,x
sta COLOR+$300,x
inx
bne @color_loop
rts
; ============================================================================
; setup_screen — clear screen, set color RAM, init gap buffer with test content
; ============================================================================
setup_screen:
; Clear screen RAM
ldx #0
lda #SCR_SPACE
@clr:
sta SCREEN+$000,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$300,x
inx
bne @clr
; GAP_END = work_buf_end (entire buffer is gap to start with)
lda #<work_buf_end
sta GAP_END
lda #>work_buf_end
sta GAP_END+1
; Copy test content into work_buf.
lda #<test_buffer
sta WORK_PTR
lda #>test_buffer
sta WORK_PTR+1
lda #<work_buf
sta BUF_PTR
lda #>work_buf
sta BUF_PTR+1
@copy:
lda WORK_PTR
cmp #<buffer_end
bne @do_byte
lda WORK_PTR+1
cmp #>buffer_end
beq @copy_done
@do_byte:
ldy #0
lda (WORK_PTR),y
sta (BUF_PTR),y
inc WORK_PTR
bne :+
inc WORK_PTR+1
: inc BUF_PTR
bne @copy
inc BUF_PTR+1
jmp @copy
@copy_done:
lda BUF_PTR
sta GAP_START
lda BUF_PTR+1
sta GAP_START+1
lda #<work_buf
sta TOP_LINE
lda #>work_buf
sta TOP_LINE+1
lda #0
sta LEFT_COL
lda #$FF
sta IS_BASIC
rts
; ============================================================================
; setup_screen_blank — like setup_screen but initialises an empty gap buffer.
; Used on warm start before (optionally) loading a source file.
; ============================================================================
setup_screen_blank:
ldx #0
lda #SCR_SPACE
@clr:
sta SCREEN+$000,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$300,x
inx
bne @clr
lda #<work_buf
sta GAP_START
lda #>work_buf
sta GAP_START+1
lda #<work_buf_end
sta GAP_END
lda #>work_buf_end
sta GAP_END+1
lda #<work_buf
sta TOP_LINE
lda #>work_buf
sta TOP_LINE+1
lda #0
sta LEFT_COL
lda #$FF
sta IS_BASIC ; test buffer is BASIC - colorize on launch
rts
; ============================================================================
; modal_draw_text — paint the entire status row as a reverse-video alert bar.
;
; Used by every modal prompt (CTRL+N "save before new", quit confirm, etc.)
; so a blocking prompt is visually unmistakable from the idle status line.
;
; Entry: A = lo byte, X = hi byte of a NUL-terminated *screen-code* string
; (raw screen codes, NOT PETSCII — same format as new_dirty_text).
; Exit: Whole status row painted: string chars + trailing fill, all in
; reverse video, color MODAL_BAR_COLOR. A, X, Y clobbered.
;
; Pair with modal_exit (calls render_status) when the prompt is dismissed.
;
; Cursor parking hook: if you later want the editor's block cursor moved onto
; the prompt, do it in the caller after this returns — this routine owns the
; row's screen/color RAM only, not CURSOR_ROW/COL.
; ============================================================================
modal_draw_text:
sta WORK_PTR
stx WORK_PTR+1
ldy #0
@mdt_str:
lda (WORK_PTR),y
beq @mdt_fill ; NUL -> fill remainder of the row
ora #SCR_REVERSE ; reverse video for the alert bar
sta STATUS_ROW,y
lda #MODAL_BAR_COLOR
sta COLOR,y
iny
cpy #COLS
bne @mdt_str
rts ; string filled the whole row exactly
@mdt_fill:
; Remainder of the row: reversed space = solid color block.
cpy #COLS
beq @mdt_done
lda #SCR_SPACE | SCR_REVERSE
sta STATUS_ROW,y
lda #MODAL_BAR_COLOR
sta COLOR,y
iny
bne @mdt_fill ; Y < 40, always taken
@mdt_done:
rts
; ============================================================================
; modal_exit — dismiss a modal prompt and restore the idle status bar.
; Single choke point so the restore (and any future cursor un-park) lives in
; one place. Clobbers A, X (via render_status).
; ============================================================================
modal_exit:
jmp render_status ; tail-call: repaints the whole status row
render_status:
ldx #0
@loop:
cpx #COLS ; never write past the 40-column row, even if
beq @done ; status_text is too long (clamps overflow)
lda status_text,x
beq @pad
jsr lookup_screen
sta STATUS_ROW,x
lda #DEFAULT_STATUS_COLOR
sta COLOR,x
inx
bne @loop ; X < 40 always, so this is just "loop"
@pad:
; Blank the rest of the status row
lda #SCR_SPACE
@pad_loop:
cpx #COLS
bcs @done ; stop at or past COLS (bcs, not beq: if X
sta STATUS_ROW,x ; somehow entered >= COLS we still terminate)
lda #DEFAULT_STATUS_COLOR
sta COLOR,x
lda #SCR_SPACE
inx
bne @pad_loop
@done:
rts
status_text:
; Must fit in COLS (40) columns. render_status now clamps overflow, but
; keep it short so nothing spills onto content row 1.
.byte "f3=load f5=save f6=src f7=quit f8=mod", 0
; Add the load/save routines
.include "loadsave.asm"
; ============================================================================
; Settings popover — F2
;
; Layout (screen rows 2–11, cols 1–38):
;
; row 2: +------------------------------------+
; row 3: | petproject settings f2=close |
; row 4: | |
; row 5: | drive: [ 8] < > |
; row 6: | border: [ 6] < > ## |
; row 7: | background: [ 0] < > ## |
; row 8: | text color: [ 14] < > ## |
; row 9: | cursor blink: [ on] < > |
; row 10: | |
; row 11: +------------------------------------+
;
; SETTINGS_ROW selects the highlighted row (0=drive .. 4=blink).
; Left/right arrows change the value with wraparound.
; Up/down arrows move between rows.
; F2 or RUN/STOP closes. Changes apply live and are saved on close.
;
; Screen-writing helpers use WORK_PTR as a screen RAM pointer and
; TMP as scratch — safe since no editor operations run while open.
; ============================================================================
; Row indices
SET_ROW_DRIVE = 0
SET_ROW_BORDER = 1
SET_ROW_BG = 2
SET_ROW_FG = 3
SET_ROW_BLINK = 4
SET_ROW_MAX = 4 ; highest valid row index
; Popover screen geometry (all 0-based, row 0 = status bar)
POP_TOP = 2 ; first row of popover box
POP_LEFT = 0 ; left edge column (bar at col 0)
POP_WIDTH = 38 ; inner width (cols 1-38, right bar at col 39)
POP_INNER_LEFT = 1 ; left edge of text inside box
; Color RAM color for popover chrome vs highlighted row
POP_CHROME_CLR = $0B ; dark grey — box lines, labels
POP_HILITE_CLR = $01 ; white — selected row
POP_VALUE_CLR = $03 ; cyan — value fields
POP_SWATCH_COL = 32 ; screen column of color swatch (2 chars)
; ============================================================================
; do_settings_popover — entry point, called from main_loop F2 handler
; ============================================================================
do_settings_popover:
lda #0
sta SETTINGS_ROW ; start on first row
jsr settings_draw_all
; Flush keyboard buffer — without this, the F1 that opened the popover
; is still in the buffer and immediately closes it again.
@flush:
jsr GETIN
bne @flush
@pop_loop:
jsr GETIN
beq @pop_loop
cmp #KEY_F1
beq @pop_close
cmp #PET_STOP
beq @pop_close
cmp #KEY_CRSR_UP
bne @try_pop_dn
jsr settings_row_up
jmp @pop_redraw
@try_pop_dn:
cmp #KEY_CRSR_DN
bne @try_pop_lt
jsr settings_row_down
jmp @pop_redraw
@try_pop_lt:
cmp #KEY_CRSR_LT