4
\$\begingroup\$

This procedure is the natural continuation of the Example 4 that I wrote for How buffered input works, a Stack Overflow Q/A.

Features include:

  • freely positioning the cursor.
    • Left Moves cursor left.
    • Right Moves cursor right.
    • Home Moves cursor to the far left.
    • End Moves cursor to the far right.
    • CtrlLeft Move cursor to word on the left.
    • CtrlRight Move cursor to word on the right.
  • deleting text in numerous ways.
    • Delete Removes the current character or the selected text.
    • Backspace Removes the character to the left of the cursor or the selected text.
    • CtrlHome Removes all characters to the left.
    • CtrlEnd Removes all characters to the right.
  • cutting, copying and pasting on a clipboard.
    • ShiftLeft Extend selection to the left.
    • ShiftRight Extend selection to the right.
    • ShiftHome Extend selection to the far left.
    • ShiftEnd Extend selection to the far right.
    • ShiftCtrlLeft Extend selection to word on the left.
    • ShiftCtrlRight Extend selection to word on the right.
    • CtrlX Cut to clipboard.
    • CtrlC Copy to clipboard.
    • CtrlV Paste from clipboard.
  • undo/redo of the most recently deleted text.
    • CtrlZ Undo deleted text.
  • easy character case flipping.
    • CtrlO Other case of selection.
  • using an optional input history.
    • Up Fetch previous history item.
    • Down Fetch next history item.
  • ending input in several manners allowing for complex dialogs.
    • Return Ends input altering the input buffer and the history.
    • Escape Ends input altering the input buffer.
    • Tab Ends input altering the input buffer.
    • ShiftTab Ends input altering the input buffer.

If the 8th byte of the input buffer holds a non-zero value then the storage space is supposed to contain an old string (perhaps from a previous input). The old string is not required to be zero terminated and is immediately shown on screen.

While the input is in progress, the input is confined to staying within the user specified input box on the current row. Longer texts will scroll horizontally.
When the input is finally done, either the complete text or at least the right hand part of it is shown in the input box, leaving the cursor beyond the last character.

The first key to process and the last key processed are of particular interest in building complex dialogs. They are passed through the AX register.

This procedure does no ctrlC checking.

This procedure supports input redirection.

; Example 5, Rich Edit Form Input. (FASM 1.54)

; IN (ds:dx,es:bx,ax) OUT (ax)
FormInput:
; Silently quits on parameter errors, just like DOS

; Entry DS:DX   Buffer of max 8+128 bytes
;               1st dword is far pointer to optional history
;               (History is collection of ASCIIZ strings plus a final zero)
;               5th byte is size of history in 256 bytes pages
;               6th byte is size of input box
;               7th byte is size of storage space starting at 9th byte
;               8th byte is size of old ASCII(Z) string, 0 if none
;               Storage space can contain old ASCII(Z) string
;       ES:BX   Clipboard (minimum 128 bytes)
;       AX      First key to process, 0 if none
; Exit  DS:DX   Nothing changed if any parameter was invalid
;               8th byte is size of new ASCIIZ string
;               Storage space contains new ASCIIZ string
;               New ASCIIZ string uniquely appended to history
;       AX      Last key processed
; Local [bp-1]  PAGE    Display page
;       [bp-2]  STORE   Size of storage space
;       [bp-3]  ROW     Row of input box
;       [bp-4]  COL     Column of input box
;       [bp-5]  SHIFT   Number of characters shifted out on leftside
;       [bp-6]  INBOX   Size of input box
;       [bp-7]  LIX     Number of characters in current input string
;       [bp-8]  CIX     Position of cursor in current input string
;       [bp-10] FLAGS   Bit[0] is ON for normal keyboard input
;                       Bit[1] is ON if selection exists
;       [bp-11] HISEL   Position of high end of selection
;       [bp-12] LOSEL   Position of low end of selection
;       [bp-14] HISLIX  Number of strings in history
;       [bp-16] HISCIX  Index of current string in history
;       [bp-18] OLDKEY  Key previously processed

        pusha
        sub     sp, 268                 ;EditBuffer (128) and
        mov     bp, sp                  ; UndeleteBuffer (12+128)
        pusha                           ;'sub sp, 16' Local variables
        push    0                       ;OLDKEY

        mov     si, dx
        add     si, 6
        lodsw                           ; -> SI points at storage space
        dec     al                      ;AL is size of storage space
        js      .Quit                   ;Storage space not in [1,128]!
        inc     al
        cmp     ah, al                  ;AH is size of old string
        jnb     .Quit                   ;Old string too long!

        xor     di, di
        push    es                      ;(1)
        les     bx, [si-8]              ;FarPtrOfHistory
        mov     ch, [si-4]              ;SizeOfHistory (in pages)
        xor     cl, cl
        jcxz    .c                      ;It's fine to not use an history
.a:     cmp     byte [es:bx], 0
        je      .c                      ;Found final zero
        inc     di                      ;Counts strings
.b:     inc     bx
        dec     cx                      ;Room for terminating zero ?
        jz      .Quit_                  ;No
        cmp     byte [es:bx], 0
        jne     .b
        inc     bx
        dec     cx                      ;Room left for final zero ?
        jnz     .a                      ;Yes, go check
.Quit_: pop     es                      ;(1a)
.Quit:  lea     sp, [bp+268]            ;Free Locals, EditBuffer,
        popa                            ; and UndeleteBuffer
        ret

.c:     pop     es                      ;(1b)
        mov     [bp-14], di             ;HISLIX
        mov     [bp-16], di             ;HISCIX

        mov     bl, al
        mov     ah, 0Fh                 ;BIOS.GetVideoMode
        int     10h                     ; -> AL=Mode AH=Cols BH=Page
        mov     [bp-2], bx              ;STORE and PAGE
        mov     bl, ah
        mov     ah, 03h                 ;BIOS.GetCursor
        int     10h                     ; -> CX=Shape DL=Col DH=Row
        mov     [bp-4], dx              ;COL and ROW
        sub     bl, dl                  ;Size of the widest inbox
        movzx   cx, byte [si-3]         ;Requested size inbox
        jcxz    .d                      ;It's fine to not want to set the
        cmp     bl, cl                  ; size for the input box
        jb      .d
        mov     bl, cl
.d:     mov     [bp-6], bl              ;INBOX

        call    .Wipe                   ; -> CIX=0 LIX=0 SHIFT=0
        xor     bx, bx                  ;STDIN
        mov     ax, 4400h               ;DOS.GetDeviceInformation
        int     21h                     ; -> AX DX CF
        jc      .e                      ;Go default to keyboard
        test    dl, dl
        jns     .f                      ;Block device, not keyboard
        shr     dl, 1
.e:     adc     bx, bx                  ; -> BX=1 if Keyboard
.f:     mov     [bp-10], bx             ;FLAGS

        mov     cl, [si-1]              ;CH=0 Size of old string
        jmp     .h
.g:     lodsb                           ;Storage space gives old string
        push    cx si                   ;(2)
        call    .Asc                    ; -> CF Input old string
        pop     si cx                   ;(2)
        jc      .i
.h:     dec     cx
        jns     .g

.i:     mov     ax, [bp+268+14]         ;pusha.AX is NEWKEY
        test    ax, ax
        jnz     .Main_
.Main:  call    .Show
        mov     ax, [bp+268+14]         ;pusha.AX is NEWKEY
        mov     [bp-18], ax             ;OLDKEY
        call    .Key                    ; -> AX (DL SI)
        mov     [bp+268+14], ax         ;pusha.AX is NEWKEY
.Main_: mov     bx, .Scan
        test    ah, ah
        jz      .l                      ;Not an extended ASCII
        mov     [cs:.Fail], ax          ;Sentinel
.j:     lea     bx, [bx+4]
        mov     cx, [cs:bx-2]
        mov     dx, cx
        and     cx, 0FF01h              ;Strip flag bits
        cmp     ax, cx
        jne     .j
        mov     [bp+268+14], dx         ;pusha.AX is NEWKEY Update with
        shl     dl, 1                   ;Flag bit 7         extra flags
        jc      .k                      ;Don't undo selection
        call    .NoSel
.k:     shl     dl, 1                   ;Flag bit 6
        jnc     .l                      ;Current key is not a deletion key
        call    .Backup
.l:     call    word [cs:bx]
        jmp     .Main
; - - - - - - - - - - - - - - - - - - -
; Bit[0] is ON if <SHIFT> has to be pressed
; Bit[6] is ON if this is a deletion key
; Bit[7] is ON if selection should not be un-done automatically
.Scan:  dw             .Asc
        dw      4B00h, .s4B             ;<LEFT>
        dw      4D00h, .s4D             ;<RIGHT>
        dw      4700h, .s47             ;<HOME>
        dw      4F00h, .s4F             ;<END>
        dw      7740h, .s77             ;<CTRL-HOME>
        dw      7540h, .s75             ;<CTRL-END>
        dw      7300h, .s73             ;<CTRL-LEFT>
        dw      7400h, .s74             ;<CTRL-RIGHT>
        dw      4800h, .s48             ;<UP>
        dw      5000h, .s50             ;<DOWN>
        dw      1C00h, .s1C             ;<RETURN> or <ENTER>
        dw      0100h, .s1C             ;<ESCAPE>
        dw      0F00h, .s1C             ;<TAB>
        dw      0F01h, .s1C             ;<SHIFT-TAB>
        dw      1100h, .s11             ;<CTRL-Z> undo/redo

        dw      53C0h, .s53             ;<DELETE>
        dw      0EC0h, .s0E             ;<BACKSPACE>
        dw      4B81h, .s4Bs            ;<SHIFT-LEFT>
        dw      4D81h, .s4Ds            ;<SHIFT-RIGHT>
        dw      4781h, .s47s            ;<SHIFT-HOME>
        dw      4F81h, .s4Fs            ;<SHIFT-END>
        dw      7381h, .s73s            ;<SHIFT-CTRL-LEFT>
        dw      7481h, .s74s            ;<SHIFT-CTRL-RIGHT>
        dw      1880h, .s18             ;<CTRL-O> other case of selection
        dw      2DC0h, .s2D             ;<CTRL-X> cut to clipboard
        dw      2E80h, .s2E             ;<CTRL-C> copy to clipboard
        dw      2F80h, .s2F             ;<CTRL-V> paste from clipboard

.Fail:  dw          ?, .STC
; - - - - - - - - - - - - - - - - - - -
; Most of the complexity herein is due to getting the key from DOS,
; but for now I want input redirection capability.
.Key:   mov     ah, 07h                 ;DOS.STDINInput
        int     21h                     ; -> AL
        test    al, al                  ;Extended ASCII requires 2 calls
        jz      .Key_2
        mov     si, .Key_List           ;Translate ASCII into scancode
        mov     [cs:.Key_Fail], al      ;Sentinel
        mov     dl, al
.Key_1: lods    word [cs:si]
        cmp     al, dl
        jne     .Key_1
        test    ah, ah
        jnz     .Key_3                  ;Got scancode we can use
        cmp     al, 10                  ;Remains an ASCII
        je      .Key                    ;Silently ignoring linefeed
        ret                             ; in favor of input redirection

.Key_2: mov     ah, 07h                 ;DOS.STDINInput
        int     21h                     ; -> AL
        shl     ax, 8
        jz      .Key                    ;Don't allow embedded zeroes
.Key_3: xor     al, al
        test    word [bp-10], 1         ;FLAGS.Keyboard ?
        jz      .Key_4                  ;No, input is redirected
        push    ax                      ;(3)
        mov     ah, 02h                 ;BIOS.GetKeyboardFlags
        int     16h                     ; -> AL
        test    al, 00000011b           ;Either SHIFT key is depressed?
        pop     ax                      ;(3)
        setnz   al
.Key_4: ret

.Key_List:
        dw      1C0Dh                   ;<RETURN> or <ENTER>
        dw      011Bh                   ;<ESCAPE>
        dw      0F09h                   ;<TAB>
        dw      0E08h                   ;<BACKSPACE>
        dw      180Fh                   ;<CTRL-O> other case of selection
        dw      2D18h                   ;<CTRL-X> cut to clipboard
        dw      2E03h                   ;<CTRL-C> copy to clipboard
        dw      2F16h                   ;<CTRL-V> paste from clipboard
        dw      111Ah                   ;<CTRL-Z> undo/redo
.Key_Fail:
        db      ?, 0
; - - - - - - - - - - - - - - - - - - -
.Show:  test    word [bp-10], 1         ;FLAGS.Keyboard ?
        jz      .Show_Ready             ;No, input is redirected
        movzx   di, byte [bp-6]         ;INBOX
        movzx   si, byte [bp-5]         ;SHIFT
        mov     dx, [bp-4]              ;COL and ROW
        mov     cx, 1                   ;Replication count
        mov     bh, [bp-1]              ;PAGE
.Show_Next:
        mov     ah, 02h                 ;BIOS.SetCursor
        int     10h
        mov     bl, 07h                 ;WhiteOnBlack for normal text
        test    word [bp-10], 2         ;FLAGS.Selection ?
        jz      .Show_None
        mov     ax, si                  ;Current position
        cmp     al, [bp-12]             ;LOSEL
        jb      .Show_None
        cmp     al, [bp-11]             ;HISEL
        jae     .Show_None
        mov     bl, 1Fh                 ;BrightWhiteOnBlue for selection
.Show_None:
        mov     al, [bp+si]             ;Current character
        mov     ah, 09h                 ;BIOS.WriteCharacterAndAttribute
        int     10h
        inc     dl                      ;Next column
        inc     si                      ;Next character
        dec     di
        jnz     .Show_Next              ;Process all of the input box

        mov     dx, [bp-4]              ;COL and ROW
        add     dl, [bp-8]              ;CIX
        sub     dl, [bp-5]              ;SHIFT
        mov     ah, 02h                 ;BIOS.SetCursor
        int     10h
.Show_Ready:
        ret
; - - - - - - - - - - - - - - - - - - -
; Will backup only once for a series of deletion keys
.Backup:mov     dx, [bp-18]             ;OLDKEY
        test    dh, dh                  ;DH is zero for a character key
        jz      .Backup_First           ;Previous key is not a deletion key
        test    dl, 64                  ;DL has flags for an action key
        jnz     .Backup_Once            ;Previous key was also deletion key
.Backup_First:
        mov     si, 128                 ;Undelete buffer is at BP+128
@@:     sub     si, 2
        mov     dx, [bp+si]
        mov     [bp+128+12+si], dx
        cmp     si, -12
        jg      @b
.Backup_Once:
        ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-Z>
.s11:   mov     ax, [bp-18]             ;OLDKEY
        cmp     ax, 1100h
        je      .s11_Undo               ;Previous key was also <CTRL-Z>
        test    ah, ah                  ;AH is zero for a character key
        jz      .s11_Bad                ;Previous key is not a deletion key
        test    al, 64                  ;AL has flags for an action key
        jz      .s11_Bad                ;Previous key is not a deletion key
.s11_Undo:
        mov     si, 128                 ;Undelete buffer is at BP+128
@@:     sub     si, 2
        mov     ax, [bp+si]
        mov     dx, [bp+128+12+si]
        mov     [bp+128+12+si], ax
        mov     [bp+si], dx
        cmp     si, -12
        jg      @b
        ret
.s11_Bad:
        mov     word [bp+268+14], 0     ;pusha.AX is NEWKEY Throw away
        ret                             ;                   invalid key
; - - - - - - - - - - - - - - - - - - -
; AH=01h <ESCAPE>
; AH=0Fh <TAB> or <SHIFT-TAB>
; AH=1Ch <RETURN> or <ENTER>
.s1C:   xor     si, si
        mov     bx, [bp+268+10]         ;pusha.DX -> DS:BX
        mov     al, [bp-7]              ;LIX
        mov     [bx+7], al              ;8th byte is size of new string
        add     bx, 8
        jmp     .s1C_2
.s1C_1: mov     dl, [bp+si]
        mov     [bx+si], dl             ;Storage space receives new string
        inc     si
.s1C_2: sub     al, 1
        jnb     .s1C_1
        mov     byte [bx+si], 0         ;Terminating zero

        cmp     ah, 1Ch                 ;Don't touch history for
        jne     .s1C_12                 ; <TAB> or <ESCAPE>
        mov     dh, [bx-4]              ;Size of history (in pages)
        xor     dl, dl
        test    dx, dx
        jz      .s1C_12                 ;No history
        movzx   cx, byte [bp-7]         ;LIX
        jcxz    .s1C_12                 ;Don't add empty item
        push    es                      ;(4)
        les     di, [bx-8]              ; -> ES:DI History
        mov     si, di
        jmp     .s1C_7
.s1C_3: cmp     al, [bx]                ;DS:BX points at storage space
        jne     .s1C_6                  ;Not same first character
        push    bx si                   ;(5)
        inc     si                      ;Don't re-test first characters
.s1C_4: inc     bx
        lods    byte [es:si]            ;Compare following characters
        cmp     al, [bx]
        jne     .s1C_5
        test    al, al
        jnz     .s1C_4
        pop     bx bx                   ;(5a) Duplicate found
        jmp     .s1C_7                  ;Don't keep it
.s1C_5: pop     si bx                   ;(5b) Not a duplicate
.s1C_6: lods    byte [es:si]            ;Keep this item
        stosb
        test    al, al
        jnz     .s1C_6
.s1C_7: mov     al, [es:si]             ;Next byte in history
        test    al, al
        jnz     .s1C_3                  ;Not yet the final zero
        mov     [es:di], al             ;Temporary final zero

        inc     cx                      ;Space needed for new item
        mov     ax, di                  ;DI points beyond last item
        sub     ax, [bx-8]              ;[BX-8] points at first item
        sub     dx, ax                  ;Free=Size-(Last-First)
        cmp     cx, dx
        jb      .s1C_11                 ;Plenty space available
        mov     di, [bx-8]              ; -> ES:DI History
        mov     si, di
.s1C_8: lods    byte [es:si]            ;Discard oldest item
        inc     dx                      ;One more free byte
        test    al, al
        jnz     .s1C_8
        cmp     cx, dx
        jnb     .s1C_8                  ;Not enough, go discard another one
        jmp     .s1C_10                 ;Go keep all remaining items
.s1C_9: lods    byte [es:si]            ;Keep this item
        stosb
        test    al, al
        jnz     .s1C_9
.s1C_10:cmp     [es:si], al             ;AL=0
        jne     .s1C_9                  ;More items to keep
.s1C_11:mov     al, [bx]                ;Copy new ASCIIZ item
        stosb
        inc     bx
        test    al, al
        jnz     .s1C_11
        stosb                           ;New final zero
        pop     es                      ;(4)

.s1C_12:call    .s47                    ;<HOME>
        call    .s4F                    ;<END>
        or      word [bp-10], 1         ;FLAGS.Keyboard, Force display
        call    .Show                   ;Show all or last part of result
        jmp     .Quit
; - - - - - - - - - - - - - - - - - - -
; <BACKSPACE>
.s0E:   call    .DelSel                 ; -> CF
        jc      .s0E_1
        cmp     byte [bp-8], 0          ;CIX
        jne     .s0E_2
.s0E_1: ret
.s0E_2: call    .s4B                    ;<LEFT>
; ---   ---   ---   ---   ---   ---   --
.Del:   movzx   di, byte [bp-8]         ;CIX
        movzx   cx, byte [bp-7]         ;LIX
        sub     cx, di
        jz      .Del_No                 ;Cursor behind the current input
@@:     mov     dl, [bp+di+1]           ;Move down in edit buffer
        mov     [bp+di], dl
        inc     di
        dec     cx
        jnz     @b
        dec     byte [bp-7]             ;LIX
.Del_No:ret
; - - - - - - - - - - - - - - - - - - -
; <DELETE>
.s53:   call    .DelSel                 ; -> CF
        jnc     .Del
        ret
; - - - - - - - - - - - - - - - - - - -
.Beep:  mov     ax, 0E07h               ;BIOS.TeletypeBeep
        int     10h
.STC:   stc
        ret
; - - - - - - - - - - - - - - - - - - -
.Asc:   cmp     al, 32
        jb      .STC
        call    .DelSel                 ; -> CF
        movzx   di, byte [bp-8]         ;CIX
        movzx   si, byte [bp-7]         ;LIX
        lea     dx, [si+1]
        cmp     dl, [bp-2]              ;STORE
        jnb     .Beep                   ;Not enough storage capacity
        jmp     .Asc_2
.Asc_1: mov     dl, [bp+si-1]           ;Move up in edit buffer
        mov     [bp+si], dl
        dec     si
.Asc_2: cmp     si, di
        ja      .Asc_1
        mov     [bp+si], al             ;Add newest character
        inc     byte [bp-7]             ;LIX
; ---   ---   ---   ---   ---   ---   --
; <RIGHT>
.s4D:   inc     byte [bp-8]             ;CIX
        mov     al, [bp-7]              ;LIX
        cmp     [bp-8], al              ;CIX
        jbe     .Shift
; ---   ---   ---   ---   ---   ---   --
; <LEFT>
.s4B:   sub     byte [bp-8], 1          ;CIX
        jnb     .Shift
; ---   ---   ---   ---   ---   ---   --
; <HOME>
.s47:   xor     bl, bl
        jmp     .Shift_
; - - - - - - - - - - - - - - - - - - -
; <END>
.s4F:   mov     bl, [bp-7]              ;LIX
; ---   ---   ---   ---   ---   ---   --
.Shift_:mov     [bp-8], bl              ;CIX
.Shift: mov     dl, [bp-5]              ;SHIFT
        mov     dh, [bp-8]              ;CIX
        cmp     dh, dl
        jb      .Shi_1
        add     dl, [bp-6]              ;INBOX
        sub     dh, dl
        jb      .Shi_2
        inc     dh
        add     dh, [bp-5]              ;SHIFT
.Shi_1: mov     [bp-5], dh              ;SHIFT
.Shi_2: clc
        ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-HOME>
.s77:   call    .s0E                    ;<BACKSPACE>
        cmp     byte [bp-8], 0          ;CIX
        ja      .s77
        ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-END>
.s75:   call    .Del
        mov     al, [bp-8]              ;CIX
        cmp     al, [bp-7]              ;LIX
        jb      .s75
        ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-LEFT>
.s73:   movzx   bx, byte [bp-8]         ;CIX
        dec     bx
        call    .wChar_                 ; -> AL
@@:     shr     al, 1                   ;Make room for 2nd leftside signal
        dec     bx
        call    .wChar                  ; -> AL
        inc     bx
        and     al, 11000000b           ;Only keep both left signals
        cmp     al, 01000000b           ;Found the start of a word ?
        je      .Shift_                 ;Yes
        dec     bx
        jns     @b
        ret
; - - - - - - - - - - - - - - - - - - -
; <CTRL-RIGHT>
.s74:   movzx   bx, byte [bp-8]         ;CIX
        call    .wChar_                 ; -> AL
@@:     shl     al, 1                   ;Make room for 2nd rightside signal
        inc     bx
        call    .wChar                  ; -> AL
        and     al, 00000011b           ;Only keep both right signals
        cmp     al, 00000010b           ;Found the end of a word ?
        je      .Shift_                 ;Yes
        cmp     bl, [bp-7]              ;LIX
        jb      @b
        ret
; - - - - - - - - - - - - - - - - - - -
.wChar_:xor     al, al
.wChar: test    bx, bx
        js      @f
        cmp     bl, [bp-7]              ;LIX
        jae     @f
        mov     si, bx
        cmp     byte [bp+si], " "       ;Current character
        je      @f                      ;Whitespace
        or      al, 10000001b           ;Signal at both sides
@@:     ret
; - - - - - - - - - - - - - - - - - - -
; <UP>
.s48:   mov     cx, [bp-16]             ;HISCIX
        dec     cx
        jns     .s48_
        ret
.s48_:  mov     [bp-16], cx             ;HISCIX
        mov     bx, [bp+268+10]         ;pusha.DX points at InputBuffer
        push    es                      ;(6)
        les     bx, [bx]                ;FarPtrOfHistory
        jmp     .s48_2
.s48_1: inc     bx                      ;Skip an item in the history
        cmp     byte [es:bx-1], 0
        jne     .s48_1
.s48_2: dec     cx
        jns     .s48_1
        call    .Wipe                   ; -> CIX=0 LIX=0 SHIFT=0
        jmp     .s48_4
.s48_3: push    bx                      ;(7)
        call    .Asc                    ; -> CF Input an history string
        pop     bx                      ;(7)
        jc      .s48_5
        inc     bx
.s48_4: mov     al, [es:bx]
        test    al, al
        jnz     .s48_3
.s48_5: pop     es                      ;(6)
        ret
; - - - - - - - - - - - - - - - - - - -
; <DOWN>
.s50:   mov     cx, [bp-16]             ;HISCIX
        inc     cx
        cmp     cx, [bp-14]             ;HISLIX
        jbe     .s48_
; ---   ---   ---   ---   ---   ---   --
.Wipe:  mov     di, 128                 ;Fill edit buffer with spaces
@@:     sub     di, 2
        mov     word [bp+di], "  "
        jnz     @b
        mov     [bp-8], di              ;DI=0 -> CIX=0 LIX=0
        mov     byte [bp-5], 0          ;SHIFT=0
        ret
; - - - - - - - - - - - - - - - - - - -
; Other case of selection
; <CTRL-O>
.s18:   bt      word [bp-10], 1         ;FLAGS.Selection ?
        jnc     .s18_None
        mov     cx, [bp-12]             ;LOSEL and HISEL
.s18_1: movzx   si, cl
        mov     al, [bp+si]             ;From edit buffer
        cmp     al, "A"
        jb      .s18_3
        cmp     al, "Z"
        jbe     .s18_2
        cmp     al, "a"
        jb      .s18_3
        cmp     al, "z"
        ja      .s18_3
.s18_2: xor     al, 32                  ;Change the case
        mov     [bp+si], al
.s18_3: inc     cl
        cmp     cl, ch
        jb      .s18_1
.s18_None:
        ret
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-LEFT>
.s4Bs:  mov     ax, .s4B                ;<LEFT>
        jmp     .Sel
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-RIGHT>
.s4Ds:  mov     ax, .s4D                ;<RIGHT>
        jmp     .Sel
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-HOME>
.s47s:  mov     ax, .s47                ;<HOME>
        jmp     .Sel
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-END>
.s4Fs:  mov     ax, .s4F                ;<END>
        jmp     .Sel
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-CTRL-LEFT>
.s73s:  mov     ax, .s73                ;<CTRL-LEFT>
        jmp     .Sel
; - - - - - - - - - - - - - - - - - - -
; <SHIFT-CTRL-RIGHT>
.s74s:  mov     ax, .s74                ;<CTRL-RIGHT>
; ---   ---   ---   ---   ---   ---   --
.Sel:   mov     ch, [bp-8]              ;CIX
        call    ax                      ;One of the cursor movement ops
        mov     cl, [bp-8]              ;CIX
        cmp     cl, ch                  ;Current vs previous position
        je      .Sel_Nop                ;Cursor didn't move at all
        bts     word [bp-10], 1         ;FLAGS.Selection ?
        jc      .Sel_Old
.Sel_New:
        cmp     cl, ch                  ;Normalize the indexes
        jb      @f
        xchg    cl, ch
@@:     mov     [bp-12], cx             ;LOSEL and HISEL
.Sel_Nop:
        ret
.Sel_Old:
        mov     dx, [bp-12]             ;LOSEL and HISEL
        cmp     ch, dl                  ;Previous cursor was
        je      @f                      ; either at LOSEL or at HISEL
        mov     dh, cl                  ;Update HISEL
        mov     cl, dl                  ;Replaces a 'jmp'
@@:     mov     dl, cl                  ;Update LOSEL
        mov     cx, dx
        cmp     dl, dh                  ;Compare new borders
        jne     .Sel_New                ;Selection continues to exist
        mov     [bp-12], cx             ;Selection dies with LOSEL=HISEL
; ---   ---   ---   ---   ---   ---   --
.NoSel: and     word [bp-10], -3        ;FLAGS.Selection OFF
        ret
; - - - - - - - - - - - - - - - - - - -
; Cut to clipboard
; <CTRL-X>
.s2D:   call    .s2E                    ;<CTRL-C>
; ---   ---   ---   ---   ---   ---   --
.DelSel:btr     word [bp-10], 1         ;FLAGS.Selection ?
        jnc     .DelSel_None
        mov     bx, [bp-12]             ;LOSEL and HISEL
        call    .Shift_
@@:     call    .Del
        dec     bh
        cmp     bh, bl
        ja      @b
        mov     [bp-12], bx             ;LOSEL and HISEL
        stc                             ;CF=1 if selection existed
.DelSel_None:
        ret
; - - - - - - - - - - - - - - - - - - -
; Copy to clipboard
; <CTRL-C>
.s2E:   bt      word [bp-10], 1         ;FLAGS.Selection ?
        jnc     .s2E_None
        mov     di, [bp+268+8]          ;pusha.BX -> ES:DI is Clipboard
        mov     cx, [bp-12]             ;LOSEL and HISEL
@@:     movzx   si, cl
        mov     al, [bp+si]             ;From edit buffer
        stosb                           ; to clipboard
        inc     cl
        cmp     cl, ch
        jb      @b
        xor     al, al
        stosb
.s2E_None:
        ret
; - - - - - - - - - - - - - - - - - - -
; Paste from clipboard
; <CTRL-V>
.s2F:   call    .DelSel                 ; -> CF
        mov     si, [bp+268+8]          ;pusha.BX -> ES:SI is Clipboard
        jmp     .s2F_2
.s2F_1: push    si                      ;(8)
        call    .Asc                    ; -> CF Input the clipboard string
        pop     si                      ;(8)
        jc      .s2F_3
.s2F_2: lods    byte [es:si]            ;From clipboard
        test    al, al
        jnz     .s2F_1
.s2F_3: ret
; --------------------------------------
  • The idea behind this all new input procedure (input as in a dialog window) is to pimp my DOS programs with input capabilities that rival those found on Windows. It's possible that my observations on what Windows has to offer is missing a thing or two. Hence my question:

    Did I include enough (Windows style) features to rightfully call this procedure "Rich Edit" ?

  • Since I program for DOS, I can't afford to waste memory. I tried to write the shortest code possible, but perhaps someone else could suggest additional code size reductions?

  • The current code uses a lot of memory transfers (buffer to buffer). I would like these to be as fast as possible, but I don't want the code size to increase. Kind of a dilemma but maybe a seasoned programmer can find a way out?

\$\endgroup\$
  • 3
    \$\begingroup\$ What kind of assembly? It looks to my untrained eye like x86 of some kind; the linked question says x86-16 and dos - is that the case here? Consider additional tags. \$\endgroup\$ – Toby Speight Jan 22 '18 at 9:48
  • 3
    \$\begingroup\$ Please give some idea what the intended general use is. I get the idea it's for field entry for database items as in your Buffered input example. In that case, it is over capable, but for text like word processing, it could use some other functionality. \$\endgroup\$ – Shift_Left Jan 22 '18 at 13:32
  • \$\begingroup\$ @TobySpeight Code Review has very few tags available. I had tried all of these: x86, DOS, input, form, dialog. I finished by putting (x86-16) in the title. Not sure if that's OK since I seldom see this. \$\endgroup\$ – Sep Roland Jan 28 '18 at 16:11
2
\$\begingroup\$

Silently quits on parameter errors, just like DOS

Why would you do that? Please don't follow DOS' bad example. At the very least report parameter errors by setting the carry flag.


Passing too many parameters via registers IMO is never a good thing. I think you should extend the input buffer's header to 12 bytes, and move that clipboard ES:BX far pointer in there. Passing the first key to process and returning the last key processed through AX is a good choice though.


        call .Asc             ; -> CF Input old string
        pop  si cx            ;(2)
        jc   .i            ???

  .s0E: call .DelSel          ; -> CF
        jc   .s0E_1        ???

  .s53: call .DelSel          ; -> CF
        jnc .Del
        ret                ???

        call .Asc             ; -> CF Input an history string
        pop  bx               ;(7)
        jc   .s48_5        ???

        call .Asc             ; -> CF Input the clipboard string
        pop  si               ;(8)
        jc   .s2F_3        ???

There seems to be a recurring problem of not stating what the CF stands for upon returning from a few subroutines. To understand the code I had to find and examine those subroutines when simple comments like "Abort on invalid key or out of space" and "Existing selection got deleted" would have made my reviewing a lot easier.


        and  cx, 0FF01h       ;Strip flag bits

This comment suggests that all of the flag bits are to be removed, yet you keep Bit[0]. Perhaps better write "Strip some flag bits" or "Strip the extra flag bits". I'd prefer the latter because it corresponds best with the comment you wrote a few lines after.


.NoSel: and  word [bp-10], -3 ;FLAGS.Selection OFF

I think not too many people would instantly recognize this -3 as the correct value to clear the second bit. Clearer choices include:

and  word [bp-10], 1111'1111'1111'1101b
and  word [bp-10], 0FFFDh
and  word [bp-10], not 2

Answering your questions

Did I include enough (Windows style) features to rightfully call this procedure "Rich Edit" ?

I could definitely say this is indeed "Rich Edit". There's but one feature that I feel is missing. Why didn't you implement the insert/overwrite mode for adding characters?


Since I program for DOS, I can't afford to waste memory. I tried to write the shortest code possible, but perhaps someone else could suggest additional code size reductions?

You can shave off 1 byte in the sequence mov al, [bp-8] cmp al, [bp-7].
Read both variables in AX (mov ax, [bp-8]), then compare both register halves (cmp al, ah).


The current code uses a lot of memory transfers (buffer to buffer). I would like these to be as fast as possible, but I don't want the code size to increase. Kind of a dilemma but maybe a seasoned programmer can find a way out?

Speeding up the many loops might be futile since in the context of inputting the user at the keyboard is the real bottleneck. This said, the loops at .Backup_First, .s11_Undo, and .Wipe can easily be sped up by using dwords instead of words. This of course comes at the cost of adding several Operand Size Prefixes to the code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.