-
Notifications
You must be signed in to change notification settings - Fork 2
/
vimode.s
3306 lines (3253 loc) · 73.6 KB
/
vimode.s
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
; Copyright 2022 Micah J Cowan <[email protected]>
; MIT license (see accompanying LICENSE.txt)
.macpack apple2
WNDBTM = $23
CH = $24
CV = $25
BASE = $28
INVFLAG = $32
PROMPT = $33
CSW = $36
LINNUM = $50 ; line number stored here after LINGET parse
TXTTAB = $67
CURLIN = $75
LOWTR = $9B ; FINDLIN puts the pointer to a line here when found
FAC = $9D
ARG = $A5
SGNCPR = $AB
CHRGET = $B1
CHRGOT = $B7
TXTPTR = $B8
IN = $200
OURCH = $57B ; screen-hole that holds the CH value when 80-col fw is active
OURCV = $5FB ; screen-hole that holds the CV value when 80-col fw is active
TOK_TABLE = $D0D0
FNDLIN = $D61A ; finds the location of a line from its number
GETCHR = $D72C ; not to be confused with CHRGET in ZP. Gets chr from (FAC),y
LINGET = $DA0C ; parses a line number from TXTPTR
FMULTT = $E982
FDIVT = $EA69
MOVAF = $EB63 ; MOVAF is the name in Apple Programmer's Handbook.
; Copies FAC -> ARG
FLOAT_2 = $EBA0
INT = $EC23
FOUT = $ED34
VTAB = $FC22
VTABZ = $FC24
SCROLL = $FC70
CLREOL = $FC9C
WAIT = $FCA8
RDKEY = $FD0C
KEYIN = $FD1B
CROUT = $FD8E
PRBYTE = $FDDA
COUT = $FDED
COUT1 = $FDF0
SETINV = $FE80
SETNORM = $FE84
BELL = $FF3A
RET_RDCHAR = $FD37
RET_GETLN = $FD77
RET_AS_INLINE = $D532
RET_AS_RESTART = $D443
;
STAT_BASE = $750 ; line 22
STRC_BASE = $7D0 ; line 23 (last line)
PromptNormalChar = $AD ; '-'
PromptCaptureChar= $A3 ; '#'
;DEBUG=1
.ifndef DEBUG
kMaxLength = $FE
.else
;kMaxLength = $30 ; 48
kMaxLength = $FE
.endif
.org $6000
Input:
cld
InputRedirFn = * + 1
; The address of the following JMP will be _rewritten_
; to jump directly to keyboard inputs, when GETLN
; has been detected (and replaced). It will be restored
; to CheckForGetline once our replacement GETLN routine
; has exited (so that future inputs once again check for
; GETLN)
jmp CheckForGetline
jmp CheckForGetline ; we say it twice, so the bootstrapper
; knows what it should look like :)
RealInput:
; keyboard input. This immediate-jmp routine exists
; to make it easy to swap the keyboard input for some other
; KSW routine, as needed.
RealInputHigh = * + 2
jmp KEYIN
.ifdef DEBUG
PrintState:
bit StatusBarOn
bmi @good
rts
@good:
stx SaveX
; Save screen coordinates, and go to top-left
lda CH
sta SavedCH
lda #0
sta CH
lda #<STAT_BASE
sta BASE
lda #>STAT_BASE
sta BASE+1
lda INVFLAG
pha
jsr SETINV
; Print X and LL
ldx SaveX
txa
jsr PRBYTE
lda #$A0
jsr COUT
lda LineLength
jsr PRBYTE
lda #$BD
jsr COUT
; Print a portion of the buffer
ldx #0
@lp:lda IN,x
cpx SaveX
beq @xPr
jsr PRBYTE
@sp:lda #$A0
jsr COUT
inx
cpx #$0A ; ten chars of buffer (ignore LL)
bcc @lp
pla ; restore whatever inversion state from before
sta INVFLAG
; Restore screen coords and peace out
lda SavedCH
sta CH
ldx SaveX
jmp VTAB
@xPr:
pha
jsr SETNORM
pla
jsr PRBYTE
jsr SETINV
jmp @sp
StatusBarOn:
.byte 0 ; off by default
StatusBarSetup:
; Ensure the current line is above the status display area
@again:
lda CV
cmp #$16
bcc @setwbt
sbc #1 ; we already know carry is set (borrow = clear)
sta CV
jsr SCROLL ; does VTAB
jmp @again
@setwbt:
lda #$16
sta WNDBTM
rts
StatusBarCleanup:
; We just switched status bar off; go and erase it
lda CH
sta SavedCH
lda #0
sta CH
lda #$16
jsr VTABZ
jsr CLREOL
lda #$17
jsr VTABZ
jsr CLREOL
lda SavedCH
sta CH
lda CV
jsr VTABZ
lda #$18
sta WNDBTM
rts
ToggleStatusBar:
lda StatusBarOn
eor #$FF
sta StatusBarOn
bmi @statusOn
; status bar switched off; clean it up
jmp StatusBarCleanup
@statusOn:
jmp StatusBarSetup
;
PrintStackTraceSuccess:
jsr StackTraceSetup
lda ViPromptIsBasic
bmi @basic
; indicate that we did detect GETLN
lda #$D9 ; Y
jsr COUT
lda #$C5 ; E
jsr COUT
lda #$D3 ; S
jsr COUT
lda #$A0 ; SPC
jsr COUT
jmp PrintStack
@basic:
lda #$C2 ; B
jsr COUT
lda #$C1 ; A
jsr COUT
lda #$D3 ; S
jsr COUT
lda #$A0 ; SPC
jsr COUT
jmp PrintStack
PrintStackTraceFailed:
jsr StackTraceSetup
; indicate that we did NOT detect GETLN
lda #$CE ; N
jsr COUT
lda #$CF ; O
jsr COUT
lda #$A0 ; SPC
jsr COUT
lda #$A0 ; SPC
jsr COUT
jmp PrintStack
StackTraceSetup:
; save position
lda BASE
sta StSvBASE
lda BASE+1
sta StSvBASE+1
lda CH
sta StSvCH
; set position
lda #0
sta CH
lda #<STRC_BASE
sta BASE
lda #>STRC_BASE
sta BASE+1
; force direct screen output for COUT -
; we don't want to be re-entering DOS hooks...
; (but save away existing CSW first)
lda CSW
sta StSvCSW
lda CSW+1
sta StSvCSW+1
lda #<COUT1
sta CSW
lda #>COUT1
sta CSW+1
rts
PrintStack:
; This is for debugging, but... calling it from within a DOS call
; will probably munge things
tsx
inx ; Skip over PrintStack's own call (heh)
inx
inx
ldy #$0A ; print 10 bytes of stack
@Lp:
lda #$A0
jsr COUT
lda $100,x
jsr PRBYTE
inx
dey
bne @Lp
; cleanup
jsr CLREOL
lda StSvBASE
sta BASE
lda StSvBASE+1
sta BASE+1
lda StSvCH
sta CH
lda StSvCSW
sta CSW
lda StSvCSW+1
sta CSW+1
rts
StSvBASE: .word 0
StSvCSW: .word 0
StSvCH:.byte 0
.endif ; DEBUG
CheckForGetline:
; The vi-mode prompt works by detecting and *replacing* what had
; been a call from the firmware `GETLN` routine at `$FD78`. `GETLN`
; isn't hookable, so instead we hook into `KSW` (the input routine),
; and *look back at the stack* to see if it looks like we were
; called, indirectly, from `GETLN`. If we find `GETLN`'s address
; (minus 1) on the stack, with the right chain of calls appearing to
; lead from it to us, then we replace `GETLN`'s return address with
; one of our own choosing, thus automatically replacing `GETLN`
; every time it's called, with our vi-mode substitute.
;
; The notion of "looks like we were called indiretly from GETLN" is
; a bit involved. We don't want to just find `GETLN`'s address in
; the last ~8 bytes of stack, because it could just be there through
; happenstance: an old stack value that's been abandoned due to a
; `RESET` perhaps, or even `PHA TYA PHA` would put it there, if the
; `A` and `Y` registers have just the right values. Too risky.
;
; But there are also multiple ways that `GETLN` can lead to calling
; the `KSW` hook routine. On the original Apple II, `GETLN` just
; calls `RDCHAR`, which calls `KSW`. At least under emulation, the
; Apple II+, unenhanced Apple IIe, and enhanced Apple IIe, all look
; the same as original Apple II.
;
; *But*. On an Apple //c, things get "fancy". The firmware location
; for `RDCHAR` is a placeholder that jumps to the "real" location,
; in `$Cxxx` ROM space somewhere, which then calls 'KSW'. This is true
; for all original ROMs I could find (from ROM 255 to the "memory
; enhanced" models). So instead of looking for `RDCHAR`'s return
; address, we need to look for a different one Then, on the
; unofficial/user-created ROM 4X, there's yet *another* caller
; address within the `$Cxxx` ROM space that's involved!
;
; So... to find a "real" indirect call from (eventually) `GETLN`,
; what we look for on the stack is (first = nearest):
;
; ????? { `RDCHAR` *or* either one or two $Cxxx addresses } `GETLN`
;
; That is, we optionally skip one irrelevant address (presumed to be
; a DOS or ProDOS wrapper), and recognize either the return address
; from `RDCHAR` (`$FD37`), or else either one or two address in
; the `$Cxxx` range, and finally (always) the return address from
; `GETLN`, `$FD77`. (We don't accept *any* address for the optional
; first/throwaway caller - it can't have $FD or $Cx in the high
; byte; we assume DOS hooks wouldn't come from there.)
sta SaveA
stx SaveX
sty SaveY
cpx #0 ; We can be certain X=0 at the start of GETLN (because it
; sets it that way). At least if its caller's not playing
; seriously dirty tricks
beq :+
jmp @checkFailed
:
.ifdef DEBUG
clc
sec
bcs @skipUninstall
; We never reach here.
; This code is here for us to hack,
; if we want to insert BRKs to see what's being reached.
; You can't type into the monitor if you're using the same
; input function you're debugging...
lda #<RealInput
sta InputRedirFn
lda #>RealInput
sta InputRedirFn+1
@skipUninstall:
.endif ; DEBUG
tsx ; get stack pointer
inx ; and point at first byte of caller above us
;;; Look at the high byte of our immediate caller
inx
lda $100,x
cmp #$C0
bcc @firstCaller ; -> this caller is < #$C000, it's a "freebie" we
; don't count; check next caller as the "real" first
; >= #$C00
cmp #$D0
bcc @secondCaller ; -> this caller is our first of up to two #$Cxxx
; callers; head to the next.
cmp #>RET_RDCHAR
beq @hiGood
jmp @checkFailed ; -> This caller matches none of our criteria. Bail.
@hiGood:
; Check the low byte.
dex
lda $100,x
cmp #<RET_RDCHAR
beq @lowGood
jmp @checkFailed ; -> Not RDCHAR after all. Bail.
@lowGood:
inx
jmp @maybeGetln ; this was RDCHAR, so next must be GETLN to pass.
@firstCaller:
; If we reach here, we're on the high byte of the first caller after
; what we assume to have been some DOS's wrapper routine, and skipped.
; to proceed, this has to be the known RDCHAR return, or something
; from $Cxxx (presumed to be some ROM's RDCHAR).
inx
inx ; move to high byte of first "real" caller.
lda $100,x
cmp #>RET_RDCHAR
bne @firstMaybeCx ; -> Wasn't (known) RDCHAR; maybe a $Cxxx?
; matched high byte of RDCHAR - does it match low?
dex
lda $100,x
cmp #<RET_RDCHAR
bne @checkFailed ; -> Not RDCHAR. Bail.
inx
jmp @maybeGetln ; this was RDCHAR, so next must be GETLN to pass.
@firstMaybeCx:
cmp #$C0
bcc @checkFailed ; -> Not RDCHAR, not $Cxxx. Match fails.
; is >= #$C000. Is it < #$D000?
cmp #$D0
bcs @checkFailed ; -> Not RDCHAR, too high to be $Cxxx. Match fails.
@secondCaller:
; we're the first of up to two $Cxxx callers. Check to see if
; there's another.
inx
inx
lda $100,x
cmp #$C0
bcc @checkFailed ; -> Not $Cxxx, and too low to be GETLN. Failed.
; is >= #$C000. Is it < #$D000?
cmp #$D0
bcc @maybeGetln ; -> Is $Cxxx. Next one must be GETLN.
; not $Cxxx. THIS must be GETLN to pass, so back up to re-check this
; caller.
dex
dex
@maybeGetln:
; if we get here, we're at the byte before what needs to be GETLN
; to successfully match.
inx ; check the low byte first
lda $100,x
cmp #<RET_GETLN
bne @checkFailed
; so far so good...
inx
lda $100,x
cmp #>RET_GETLN
bne @checkFailed
; Eureka! We have it.
; Now, swap it for ours.
lda #>(ViModeEntry-1)
sta $100,x
dex
lda #<(ViModeEntry-1)
sta $100,x
; Check to see if, additionally, GETLN was called from
; the BASIC program-entry (general) prompt
inx
inx
lda $100,x
cmp #<RET_AS_INLINE
bne @notBasic
inx
lda $100,x
cmp #>RET_AS_INLINE
bne @notBasic
inx
lda $100,x
cmp #<RET_AS_RESTART
bne @notBasic
inx
lda $100,x
cmp #>RET_AS_RESTART
bne @notBasic
; Congrats! We're direct-mode in BASIC
lda #$FF
bne @storeIsBasic
@notBasic:
lda #$00
@storeIsBasic:
sta ViPromptIsBasic
.ifdef DEBUG
; possibly print the status bar
lda StatusBarOn ; (may have come here without toggle)
bpl @nostatus
jsr PrintStackTraceSuccess
@nostatus:
.endif ; DEBUG
; ...and return.
;
; ...We're going to land in "our" ViMode, so why don't we go ahead
; and prompt for the first character?
;
; Two reasons. (1) RDCHAR will interpret special characters like
; ESC, and let the cursor go and wander off. We don't want that -
; we want to return immediaely to our prompt, which never calls
; RDCHAR (though even normal RDKEY will process ESC, on a //c or in
; 80-column mode :( )
; (2) Cosmetic. the RDCHAR above us will have already
; stored the visible character our cursor was on, if there was text
; present before the prompt was issued. It winds up looking kinda
; tacky in some situations.
;
; So we just return a "harmless" SPACE character, which we've
; arranged for our ViMode prompt to ignore and re-take the first
; "real" character.
ldx SaveX
ldy SaveY
lda SaveA
sta (BASE),y ; restore orig char over any flashing cursor thing
lda #$A0
rts
@checkFailed:
InnerKsw:
; Just do an absolutely ordeinary input fetch.
; EXCEPT, sideline any CR we receive if the prompt asks us to,
; or emit an immediate CR without actually checking user input,
; if asked to do that.
lda SidelineCrState
sta @savedState
ldy #0 ; immediately clear SidelineCrState as early as possible,
; to be very sure it can't be left set when we exit
sty SidelineCrState
cmp #2
bne @notImmediateCr
; We've been instructed to return with a CR immediately.
lda SaveA
ldy SaveY
sta (BASE),y ; Save orig screen char over flashing cursor
lda #$8D
ldx SaveX
rts ;
@notImmediateCr:
.ifdef DEBUG
; Print the status bar if it's on.
lda StatusBarOn
bpl :+
jsr PrintStackTraceFailed
.endif
@reread:
lda SaveA
ldx SaveX
ldy SaveY
jsr RealInput
.ifdef DEBUG
; DEBUG mode.
; We only check for one key: Ctrl-\ to enable the debug status bar.
; Then we print the stack info we just rejected, and check
; keypresses again.
cmp #$DC ; \
bne @notBackSlash
; \ pressed
sta SaveA
stx SaveX
sty SaveY
jsr ToggleStatusBar
jmp @reread
.endif ; DEBUG
@notBackSlash:
cmp #$8D ; CR
bne @notCr
; We received a CR. Have we been asked to sideline it?
sty SaveY
@savedState = * + 1
ldy #$00 ; OVERWRITTEN OP
beq @yAndOut ; No, just send it out
; Yes, we were asked to sideline it
sta SidelinedChar
lda #$A0 ; fake char to placate DOS/ProDOS wrapppers
@yAndOut:
ldy SaveY
@notCr:
@done:
rts
InitViModeAndGetStarted:
;lda SaveA - no, this should always be a space, since we cleared.
; We're KSW, but we're returning to restart the prompt with a "real"
; KSW. Just load SPACE and return, hopefully the DOS hook doesn't
; care the value
lda #$A0
rts
ViModeGetline:
; Call to here if you want an explicit call to _our_ GETLN.
; Print the prompt...
;
; Reset whether we're "in BASIC". Only do this when directly
; called by a user. XXX we should have a jump entry for here
lda #$00
sta ViPromptIsBasic
ViModeGetlineInternal:
; Used internally, so as not to reset whether we're in BASIC.
lda PROMPT
jsr COUT
; fall through to general initialization, and then on to insert-mode
ViModeEntry:
lda PROMPT
sta SavePrompt
; Clear any idea of BASIC "current line"
lda #$00
sta CurBasicLinePtr
sta CurBasicLinePtr+1
; Clear any "repeat count"
sta RepeatCounter
; Or undo buffer
sta UndoLineLength
sta UndoCursorPos
sta UndoSavePending
; Or "replace mode"
sta ReplaceModeFlag
sta SidelineCrState
; Fill the inbuf with CRs
lda #$8D
ldx #0
stx AppendModeFLag ; reset append flag
@lp:
sta IN,x
inx
bne @lp
; Set the current line length
stx LineLength
.ifndef TESTEOL
jsr CLREOL ; ensure that everything on our line is actually
; in the input buffer, as well as on screen...
; by clearing the line out that we're on.
.else
ldy #0
lda #$C1
: jsr COUT
iny
cpy #80
bne :-
jsr EmitYCntBsp
.endif ; ndef TESTEOL
.ifdef DEBUG
; Pre-fill the buffer when we enter for the first time, to
; present an easy playground that tests our latest features
PrefillFlag = * + 1
lda #$FF
bpl @dbail
ldx #0
ldy #0
stx SaveX
dey
@dlp:
iny
lda DbgPrefill,y
beq @dlpdn ; terminating NUL?
cmp #$80 ; X-save flag?
bne @dprnt
; flag to put X here.
stx SaveX
jmp @dlp
@dprnt:
sta IN,x
sty SaveY
jsr ViPrintChar
ldy SaveY
inx
bne @dlp
@dlpdn:
stx LineLength
ldx SaveX
jsr BackspaceFromEOL
lda #$00
sta PrefillFlag ; make sure we don't do this again at the next prompt
@dbail:
jmp PrefillDone
DbgPrefill:
; used to pre-fill the buffer when we first enter
scrcode "PRINT ",'"',"ALPHA BETA GAMMA DELTA EPSILON IOTA",'"'
.if 0
.repeat 7
.repeat 26, I
.byte $C1 + I
.endrepeat
.endrepeat
.endif ; (repeat)
.byte 0
.endif ; DEBUG
PrefillDone:
; Are we in AutoNumber mode? Then do that.
bit AutoNumberModeFlag
bpl :+
jsr DoAutoNumber
InsertMode:
.ifdef DEBUG
jsr PrintState
.endif
:
; INSERT MODE.
jsr MyRDKEY
; We enter here via return from CheckForGetLine (when GETLN
; was found)
; Did we get a printable char? Just, ehm, print it.
cmp #$A0
bcc ControlChar
@PrinableOrDEL:
cmp #$FF ; DELETE? treat like backspace
bne @Printable
jsr Backspace
jmp InsertMode
@Printable:
; We're printable! print (and store) us.
; TODO: if we're a model that doesn't have lowercase, we should
; upper-bound it too, and force to caps like Apple ][+ does.
jmp TryInsertChar
ControlChar:
.ifdef DEBUG
MaybeCtrlBackslash:
cmp #$9C ; C-\ ?
bne @nf ;-> try 'nother char
jsr ToggleStatusBar
jmp InsertMode
@nf:
.endif
MaybeCtrlA:
cmp #$81 ; C-A ?
bne @nf
jsr ToggleAutoNumber
jmp InsertMode
@nf:
MaybeCtrlP:
cmp #$90 ; C-P
bne @nf
bit ViPromptIsBasic
bpl @nf ; if we're not in AppleSoft, we should insert the ^P
lda #0
sta RepeatCounter
jsr BasicLineBack
bcc @bad
jmp EnterNormalMode ; land in normal mode, for e.g. #
@bad:
jsr BELL
jmp InsertMode
@nf:
MaybeCtrlN:
cmp #$8E ; C-N
bne @nf
bit ViPromptIsBasic
bpl @nf ; if we're not in AppleSoft, we should insert the ^N
lda #0
sta RepeatCounter
jsr BasicLineForward
bcc @bad
jmp EnterNormalMode ; land in normal mode, for e.g. #
@bad:
jsr BELL
jmp InsertMode
@nf:
MaybeCtrlG:
cmp #$87 ; C-G
bne @nf
lda #0
sta RepeatCounter
; Fetch a line of BASIC (_if_ we're a BASIC prompt)
; from the number at the start of the buffer
jsr MaybeFetchBasicLine
bcs @succeed
jmp InsertMode ; if we fail, stay in insert mode
@succeed:
jmp EnterNormalMode ; land in normal mode, for e.g. #
@nf:
MaybeCtrlL:
cmp #$8C ; C-L ?
bne @nf
jsr SaveUndoLine
jsr TypeLastLine
lda #0
sta RepeatCounter
jmp EnterNormalMode
@nf:
MaybeEsc:
cmp #$89 ; Tab? (workaround for ESC, in 80-col mode)
beq @yes
cmp #$9B ; ESC?
bne @nf ;-> try 'nother char
@yes:
jmp EnterNormalMode
@nf:
MaybeLeftArrow:
cmp #$88 ; left-arrow (backspace)?
bne @nf ;-> try 'nother char
;jsr TryGoLeftOne - no, bc ][+ doesn't have DELETE, only BS
jsr Backspace
jmp InsertMode
@nf:
;MaybeRightArrow:
; cmp #$95
; bne @nf ;-> try 'nother char
; jsr TryGoRightOne
; jmp InsertMode
;@nf:
MaybeCtrlX:
cmp #$98
bne MaybeCtrlXOut ;-> try 'nother char
DoAbortLine:
jsr PrintRestOfLine
ldx LineLength
lda #$A0
jsr COUT
lda #$DC ; '\'
jsr COUT
lda #$8D ; CR
jsr COUT
jmp ViModeGetlineInternal
MaybeCtrlXOut:
;
MaybeCtrlV:
cmp #$96
bne @nf ;-> try 'nother char
; do a direct read, and insert it, whatever it may be
jsr MyRDKEY
jmp TryInsertChar
@nf:
MaybeCtrlZ:
cmp #$9A
bne @nf ;-> try 'nother char
jsr ShowVersion
jmp InsertMode
@nf:
MaybeCR:
cmp #$8D
beq DoCR
IMUnrecognizedControl:
; Unrecognized: we print and store uncrecognized control chars too
jmp TryInsertChar
NoRoomLeft:
NoRoomRight:
;jsr BELL
jmp InsertMode
DoCR:
jsr PrintRestOfLine ; jump to end
jsr CLREOL
jsr SaveTypedLine
jsr MaybeRecordLineNumber
;
ldx LineLength
; We've now moved both the input cursor and the on-screen cursor
; safely to the end of the line. It is now safe to "receive"
; a CR at input. Force that to happen (delayed from the one we
; sidelined)
ldy #2
sty SidelineCrState
jsr RDKEY
;
cmp #$88
bne @noRestartPrompt
; If we got here, ProDOS saw the "user" (our delayed KSW) "type"
; a CR, checked for and found a ProDOS command, executed it,
; attempted to empty our input buffer by resetting the X-reg,
; and told us the user just typed a BS, to get us to re-emit the
; prompt. Be a good boy and do as ProDOS expects.
; Emit the BS, as GETLN would.
jsr COUT
; Turn off auto-incremented line numbers - the user clearly
; just typed a line without a number... even if we never got
; to see it.
lda #0
sta AutoNumberModeFlag
jsr CROUT ; Send a CR, as GETLN would
jmp ViModeGetlineInternal
@noRestartPrompt:
lda #$8D ; CR
sta IN,x ; make damn sure we're locked off with a CR
; If we're running under DOS, and the input buffer contains a DOS
; command, the following JSR may never return, as DOS will
; reset the stack pointer. Make sure we do any necessary cleanup
; BEFORE this point!
jsr COUT ; ...and emit one, as GETLN would.
rts
BackspaceFromEOL:
; Now backspace back again to where we actually are, so next input
; prompts in the right place
stx SaveX
lda LineLength
sec
sbc SaveX
tay
EmitYCntBsp:
lda #$88
EmitYCntAReg:
cpy #$0
beq @doneBk
@lpBk:
jsr COUT
dey
bne @lpBk
@doneBk:
rts
EmitYCntSpaces:
lda #$A0
bne EmitYCntAReg
; ^ eventual RTS.
; MakeYRegRoom
; makes Y chars' room at current X position, by copying things in line
; on return, Y-reg holds ACTUAL amount of room made
; DOESN'T UPDATE THE SCREEN, DO THAT YOURSELF
MakeYRegRoom:
cpy #0
bne :+
rts
:
stx @saveX
sty @finalY
lda #kMaxLength
sec
sbc LineLength
cmp @finalY ; kMaxLength - LineLength >= char count?
bcs @plentyOfRoom
; insufficent room; alter the count -> (kMaxLength - X) then.
sta @finalY
cmp #$00
beq @end
@plentyOfRoom:
ldx LineLength
txa
clc
adc @finalY
sta LineLength
tay
lda @saveX
clc
adc @finalY
sta @stopY
@copy:
lda IN,x
sta IN,y
dey
dex
@stopY = * + 1
cpy #$00 ; OVERWRITTEN
bcs @copy
@end:
@finalY = * + 1
ldy #$00 ; OVERWRITTEN
@saveX = * + 1
ldx #$00 ; OVERWRITTEN
rts
TryReplaceChar:
cpx #kMaxLength
bne :+
jmp NoRoomRight
:
sta IN,x
inx
jsr ViPrintChar
; Are we past the end of the line? If so, extend.
cpx LineLength
bcc :+
stx LineLength
:
jmp InsertMode
TryInsertChar:
; Check to see if there's room for the char
sta SaveA
bit ReplaceModeFlag
bmi TryReplaceChar ; -> We got here via Ctrl-R - do a replace instead!
lda LineLength
cmp #kMaxLength
bcc InsertOk
jmp NoRoomRight ; No more space left!
; If we're here we are definitely inserting
InsertOk:
; Check to see if there's an undo-save pending
bit UndoSavePending
bpl @noSave
jsr SaveUndoLine
inc UndoSavePending ; relies on it being #$FF
@noSave:
; Now make some space in the buffer
stx SaveX
ldx LineLength
inx
@lp:
dex
lda IN,x
inx
sta IN,x
dex
cpx SaveX
bne @lp
; Increase line length
inc LineLength
; Insert the character to buffer
lda SaveA