
**********************************************************************
files: Editing.TXT, Editing.TAP
       func-key.asm, editline.asm
       http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8710pp.zip
**********************************************************************


** joining & splitting BASIC lines
   ===============================

Here's a routine to join & split lines of BASIC. The vertical cursor motion code is based on the routine "Edit Key" by M.Loftus from Your Spectrum magazine, although much reorganised & condensed; the joining & split code is new. LOAD at 65023, RANDOMIZE USR 65144 to enable, 65154 to disable.

Only for use with the 48k editor!

Up/down arrow now move up/down one line in the editing line (come out of editing mode to scroll up/down through the full listing). If at the top of the editing line then up will move to the extreme left; pressing up again will join the line to the previous line (if there is one). If at the bottom of the editing line then down will move to the extreme right; pressing down again will join the line to the next line (if there is one). The intervening ENTER character is replaced with a ":" and the 4-byte header of the second line is replaced with spaces.

Use the "<>" key (SymShift-W) to split a line; the cursor must be placed between a ":" and a following command token at the time, otherwise the "<>" will just be inserted into the edit line as normal; this will also happen if the next line number in sequence is already in use, or if the indicated statement does not exist in the current PROG line.

I used a spare bit in FLAGX for extra info:
* Bit 6 is set to indicate "really in edit mode" (ie. the EDIT key has been pressed), as the bit 5 edit flag used by the ROM indicates when either editing an existing line or typing in a new one. Using either the join or split functions when there was no equivalent line in PROG didn't work very well, unsurprisingly.

There's one minor restriction of which I'm aware, in that the last line in the program can't be split, because the routine checks that there's a spare line number available before the next actual line number, and when there's no next line the check fails.

func-key.asm [code]
K48:    equ  $FDFF        ;65023
        ORG  K48
VECTOR: DEFW K48+2
        RST  $38          ;MASK_INT
        DI
        PUSH AF
        PUSH HL
        PUSH BC
        PUSH DE
        LD   A,(23560)    ;LAST_K
        cp   7            ;EDIT
        jr   nz,NOTEDIT
        ;spare bit 6 in FLAGX is used for an extra editing flag
        set  6,(IY+55)    ;set EDIT flag
NOTEDIT:cp   $0D          ;ENTER
        jr   nz,NOENTER
        res  6,(IY+55)    ;reset EDIT flag
NOENTER:bit  6,(IY+55)    ;check EDIT flag
        jr   z,FINISH     ;quit if not editing a line

        LD   HL,(23643)   ;K_CUR
        LD   BC,32
        cp   $C9          ;"<>"
        jr   z,CHECKSEP
        CP   10           ;cursor down
        jr   z,CURSDO
        CP   11           ;cursor up
        jr   nz,FINISH

CURSUP: SBC  HL,BC
        EX   DE,HL        ;DE=new cursor position
        LD   HL,(23641)   ;E_LINE
        SBC  HL,DE
        jr   c,DOMOVE
        ld   a,c          ;A =32
        cp   l
        jp   z,JOINPREV   ;cursor up : start of line : join previous
        LD   DE,(23641)   ;DE=start of line if too far up
        jr   DOMOVE

CURSDO: ADD  HL,BC
        EX   DE,HL        ;DE=new cursor position
        LD   HL,(23649)   ;WORKSP
        DEC  HL           ;back to $80 end marker
        DEC  HL           ;back to $0D ENTER
        SBC  HL,DE
        jr   nc,DOMOVE
        ld   a,c          ;A =32
        add  a,l
        jp   z,JOINNEXT   ;cursor down : end of line : join next
        LD   DE,(23649)   ;WORKSP
        dec  de
        dec  de           ;DE=end of line if too far down

DOMOVE: LD   (23643),DE   ;K_CUR
NOMOVE: LD   A,(23560)    ;LAST_K
        CP   10           ;cursor down
        LD   A,9          ;cursor right if cursor down
        jr   z,SET_K
        dec  a            ;cursor left if cursor up
SET_K:  LD   (23560),A    ;LAST_K
        set  5,(iy+1)     ;"new key" bit in FLAGS

FINISH: POP  DE
        POP  BC
        POP  HL
        POP  AF
        EI
        RET

ENABLE: LD   A,(VECTOR+1) ;65144
        SUB  1
        LD   I,A
        IM   2
        RET

DISABLE:LD   A,$3F        ;65154
        LD   I,A
        IM   1
        RET

NERROR: pop  hl
        pop  bc
        jr   FINISH

CHECKSEP:
        dec  hl
        ld   a,(hl)
        cp   ":"
        jr   nz,FINISH    ;cursor not after ":"; don't separate
        ld   hl,(23625)   ;E_PPC
        inc  hl           ;HL=new line number
        push hl
        call $196E        ;LINE_ADDR
        push de           ;DE=current line address
        call $1695        ;LINE_NO
        ex   de,hl        ;HL=next actual line number
        pop  de           ;DE=LH-line start address
        pop  bc           ;BC=RH-line line number
        sbc  hl,bc        ;check (next actual line number) > (new line number)
        jr   c,FINISH
        jr   z,FINISH
SEPARATE:
        ex   de,hl        ;HL=LH-line start address
        push bc           ;BC=RH-line line number
        inc  hl
        inc  hl           ;HL=LH-line length address
        inc  hl           ;HL=address prior to 1st LH-statement
        push hl

        ld   hl,(23641)   ;E_LINE
        ld   bc,4
        add  hl,bc        ;skip over line number
        ld   bc,(23643)   ;K_CUR
        ld   a,(bc)       ;command token to search for
        ld   e,a
        ld   d,0          ;statement counter
        dec  hl
NTOKEN: push bc
        call $198B        ;EACH_STMT; find statement with matching token
        pop  bc           ;K_CUR
        push hl
        sbc  hl,bc        ;check that E_LINE position matches
        pop  hl
        jr   z,FTOKEN     ;matched and right address
        jr   nc,NERROR    ;no match
        inc  d
        jr   NTOKEN       ;matched but wrong address

FTOKEN: pop  hl           ;HL=address prior to 1st LH-statement
        push hl
        xor  a
        ld   e,a          ;E =0
        sub  d
        ld   d,a          ;D =statement#
        call $198B        ;EACH_STMT; find statement with matching number
        jr   c,NERROR     ;no match

        ld   (hl),$0D     ;replace preceding ":" with ENTER
        inc  hl
        ld   bc,4
        call $1655        ;MAKE-ROOM; 4 bytes for RH-line header
        pop  de
        dec  de           ;DE=LH-line length address
        push hl           ;HL=LH-line end address

        sbc  hl,de
        dec  hl           ;HL=LH-line length
        ex   de,hl
        ld   c,(hl)
        ld   (hl),e
        inc  hl
        ld   b,(hl)       ;BC=original length of line
        ld   (hl),d
        ld   h,b
        ld   l,c
        sbc  hl,de
        ex   de,hl        ;DE=RH-line length
        pop  hl           ;HL=LH-line end address
        pop  bc           ;BC=RH-line line number
        inc  hl
        ld   (hl),b
        inc  hl
        ld   (hl),c
        inc  hl
        jr   NEWLEN

JOINPREV:
        ld   hl,(23625)   ;E_PPC
        call $196E        ;LINE_ADDR
        push de
        jr   JOINBOTH
JOINNEXT:
        ld   hl,(23625)   ;E_PPC
        inc  hl           ;HL=(current line number) + 1
        call $196E        ;LINE_ADDR
        push de
        ld   de,(23627)   ;VARS
JOINBOTH:                 ;addresses: DE=LH-line; HL=RH-line
        sbc  hl,de
        pop  de
        jp   z,NOMOVE     ;quit if no line to join to

        ex   de,hl        ;HL=LH-line address
        inc  hl
        inc  hl
        push hl
        ld   e,(hl)
        inc  hl
        ld   d,(hl)       ;DE=1st line text length + ENTER
        inc  hl
        add  hl,de
        dec  hl
        ld   (hl),":"     ;replace ENTER with ":"
        inc  hl
        ld   a," "
        ld   (hl),a       ;replace 2nd line number & length bytes
        inc  hl           ;with spaces
        ld   (hl),a
        inc  hl
        ld   c,(hl)
        ld   (hl),a
        inc  hl
        ld   b,(hl)
        ld   (hl),a
        inc  bc
        inc  bc
        inc  bc
        inc  bc           ;BC=2nd line text length + 4 + ENTER
        pop  hl
        ex   de,hl
        add  hl,bc        ;HL=joined length
        ex   de,hl
NEWLEN: ld   (hl),e
        inc  hl
        ld   (hl),d
        call $1097        ;CLEAR_SP
        ld   a,$0D        ;ENTER
        jp   SET_K
[/code]


**********************************************************************


** editing strings with INPUT LINE
   ===============================

This routine emulates INPUT LINE l$ but with the enhancement that the existing value in l$ is presented for editing, rather than starting blank as would be the normal action. Any simple string can be used; just POKE LNAME,CODE a$ (where a$ contains the name of the string).

editline.asm [code]
K_CUR:  equ  23643
CH_ADD: equ  23645
WORKSP: equ  23649
STKBOT: equ  23651
DF_SZ:  equ  23659

EDIT_L: ld   a,$01           ;open channel 'K'
        call $1601           ;CHAN_OPEN
        call $0d6e           ;CLS_LOWER
        ld   bc,(CH_ADD)
        push bc
        ld   de,LNAME
        ld   (CH_ADD),de
        call $28b2           ;LOOK_VARS
        jp   c,$1c2e         ;"2 - Variable not found"

        inc  hl
        ld   c,(hl)
        inc  hl
        ld   b,(hl)          ;BC=l$ length
        inc  hl
        push hl
        push bc
        call $16bf           ;SET_WORK (reset workspace)
        pop  bc
        push bc
        inc  bc
        call $0030           ;BC_SPACES (make room for l$)
        pop  bc
        pop  hl
        ld   de,(WORKSP)
        ld   a,b
        or   c
        jr   z,NOTEXT
        ldir                 ;copy l$ to INPUT workspace
NOTEXT: ex   de,hl
        ld   (hl),$0d        ;followed by ENTER
        ld   (K_CUR),hl
        ld   de,LNAME
        ld   (CH_ADD),de
        call $1c1f           ;CLASS_01
        set  7,(iy+$37)      ;FLAGX: "using INPUT LINE"
        res  6,(iy+$37)      ;FLAGX: "string result"
        set  5,(iy+$37)      ;FLAGX: "INPUT mode"
        call $0f2c           ;EDITOR
        res  5,(iy+$37)      ;FLAGX: "edit mode"

        ld   hl,(STKBOT)
        ld   de,(WORKSP)
        scf
        sbc  hl,de
        ld   b,h
        ld   c,l
        call $2ab2           ;STK_ST_$
        call $2aff           ;LET
        pop  bc
        ld   (CH_ADD),bc
        ld   a,2
        ld   (DF_SZ),a       ;reset lower screen to 2 lines
        call $0d6e           ;CLS_LOWER
        res  0,(iy+02)       ;TV_FLAG
        ret

LNAME:  defm "l$:"
[/code]


**********************************************************************


** INKEY$: No Key / Any Key
   ========================

If:
DEF FN n()=PEEK 23621+256*PEEK 23622+(INKEY$="")
then any "no key" wait can be written as simply:
GO TO FN n()
so it'll stay there until "no key" is pressed and then go on to the next line - and look, no line numbers!

And if:
DEF FN n(k$)=PEEK 23621+256*PEEK 23622+(INKEY$=k$)
then any key (or "no key") wait can be written as simply:
GO TO FN n(keychar)


**********************************************************************


** INKEY$: use with caution
   ========================

As well as dealing with the expected keys, an input routine should also handle the situation where a key other than a required key was pressed. To simplify INKEY$ checks the CAPS mode can be set on/off at the start of the program with POKE 23658,8 (on) or 0 (off), which halves the number of letter values which need to be checked. The user could alter the CAPS mode while in an INPUT statement, but this could be checked & reset afterwards, and if you're just using INKEY$ then CapsShift-2 (CapsLock) has no effect.

Judicious line number selection can simplify by using a calculated GO TO:
[code]
   2 POKE 23658,0
     ...
  35 LET q$=INKEY$: GO TO 35+(10 AND q$="y")+(20 AND q$="n")
     ...
  45 "yes" code
     ...
  55 "no" code
[/code]
So the original five lines have been reduced to one line. Also, doing this:
[code]
   2 POKE 23658,0: DEF FN c()=PEEK 23621+256*PEEK 23622
     ...
   n LET q$=INKEY$: GO TO FN c()+(10 AND q$="y")+(20 AND q$="n")
     ...
n+10 "yes" code
     ...
n+20 "no" code
[/code]
means that the code can be moved or copied without changing the GO TOs, as long as the relative spacing is maintained. (FN c() returns the current line number.)

If there is more than one set of INKEY$ option checks in sequence then a check should be included to ensure that the first option key has been released before the second option is checked, because otherwise the key may still be pressed from the first selection and then the same key will be taken as the selection for the second option as well. It's safer to include either a pressed/released check or a PEEK 23561 pause in any INKEY$ loop (23561 is the REPDEL "key repeat delay" system variable).
[code]
   n GO TO FN c()+(INKEY$="")
 n+1 LET q$=INKEY$: GO TO FN c()+(10 AND q$="y")+(20 AND q$="n")
     ... or ...
   n FOR q=1 TO PEEK 23561*5: NEXT q: LET q$=INKEY$:
     GO TO FN c()+(10 AND q$="y")+(20 AND q$="n")
[/code]
The FOR...NEXT loop (note the *5) takes the same time as a PAUSE PEEK 23561 but isn't interrupted by a key being pressed.

   ========================

   A problem with using multiple INKEY$ to check for a single response is that you can get "key drift" whereby, because INKEY$ is re-evaluated each time, the keypress recorded at the first instance may be different from the keypress recorded at the last instance; this becomes more probable the more options that are being checked and the more intervening code there is. Assigning the key value to a variable at the start of the evaluation section ensures that all subsequent references to that keypress return the same value.


**********************************************************************


** Changing the colour of the INPUT line
   =====================================

Embedded colour controls are entered with the number keys in Extend mode, with no shift for Paper and Caps shift for Ink; 8 (shifted) do Flash off & on, and 9 (no shift) do Bright; see page 87 of the 48k manual. They don't work in 128k BASIC listings, although they do work in INPUT statements.

System variable BORDCR (23624) specifies the attributes for the lower screen; the PAPER setting (bits 5,4,3) also defines the BORDER colour. As for ATTR_P (23693) and ATTR_T (23695) the format is:
	FLASH*128 + BRIGHT*64 + PAPER*8 + INK
So it is these attributes which will be used by INPUT. Prompt strings within an INPUT statement can be assigned different attributes by using embedded colour controls in the BASIC line.

So, if {hh} indicates a hex byte value of $hh, then:
	POKE 23624,1*8+7
sets the lower screen to blue PAPER and white INK
	INPUT "{12}{01}{11}{02}{10}{06}text{12}{00}{11}{07} ";a$
will display "text" in flashing yellow on red, followed by a non-flashing space in white, followed by a$ entered in white on blue with a blue border.

All of the lower screen not explicitly altered by embedded colour controls will display in the BORDCR colours, not just the INPUT line. To make the lower screen white except for the INPUT area, start the INPUT with this:
	INPUT AT 0,0;"{11}{07}{10}{00}",,,,AT 1,0; ...
The INK 0 control is just so that the BASIC text can be seen when editing the line, otherwise it would be white on white.

   =====================================

This works on the 128k, but is not ideal: To enter colour/inverse controls, just load the program in 48k mode. When re-loaded in 128k mode the colours won't show while editing but will show with LIST. However, if a line containing colour controls is edited in 128k mode then they will be lost.


**********************************************************************


** INKEY$ timings
   ==============

I did some timings in BASin for different methods of key checking.
[code]
a. IF INKEY$={value} AND {condition} THEN {action} : GO TO {line}
b. IF INKEY$={value} THEN IF {condition} THEN {action}
c. IF i$={value} AND {condition} THEN {action} : GO TO {line}
d. IF i$={value} THEN IF {condition} THEN {action}

T-states for evaluating the condition:

True                INKEY$      i$
                     a,b        c,d
a,c     AND         20474       19430
b,d     THEN IF     21412       20596

False               INKEY$      i$
                     a,b        c,d
a,c     AND         20679       19635
b,d     THEN IF     11943       10058
[/code]

A couple of other points to consider:

* GOTOs get slower the further down the program the target line number occurs, as GOTO & GOSUB searches always start from (PROG); ie. the first line of the program.

* If there's just a single key-input point in the program loop (eg. LET i$=INKEY$) then there's no possibility of the value changing during the loop. If there are multiple INKEY$ checks in the loop then, depending upon how much intervening processing there is, the value returned by INKEY$ may change (most probably from "a key" to "no key" or vice-versa), producing inconsistent results. So it will usually be better to "lock in" the last value by assigning it to a variable until all the processing related to that value has been completed.


**********************************************************************


** Keyboard scanning routine
   =========================

You could take a look at Pete Cooke's keyboard scanning routine from Tau Ceti and Micronaut One, which was published as [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8710pp.zip]Star Tip 5[/url] in Your Sinclair Oct'87.


**********************************************************************
