; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: TAsmEdit object class
;
;  Target OS: Any
;
;  Dependencies:
;
;  Notes: Advanced text editor.
;_________________________________________________________________________________________

module "TAsmEdit"


aeoNavRightAfterEOL   = 1                ; the caret can be moved to the right of the end of line.
aeoNavLeftToPrevLine  = 2                ; when the caret is moved left from position 0 it moves at the end of the previous line (if any).
aeoLeftMargin         = 4
aeoLineNumbers        = 8

aeoBorder             = $10
aeoStripedBackground  = $20

aeoScrollYMore = $40            ; scroll Y more than needed in order to scroll less and to scroll more readable.
aeoScrollXMore = $80            ; scroll X more than needed


; line flags

aelfWordWrap = 1


struct TUndoInfo
  .pNext     dd ?       ; pointer to the next TUndoInfo structure in the chain.

  .caret_x   dd ?
  .caret_y   dd ?
  .line      dd ?       ; the line number affected.
  .pText     dd ?       ; pointer to the previous TLineInfo of the affected line for chopChanged and chopDeleted
ends



rstruct TLineInfo
  .flags        dd  ?   ; line flags
  .line_num     dd  ?   ; line number
  .line_width   dd  ?   ; the latest computed line width in characters. For the wrapped lines is the width of the longest subline.
                        ;
  .debugdata    dd  ?   ; pointer to debug data, associated to this line
                        ;
  .syn_array    dd  ?   ; TArray of TTextChunk items for syntax colorization.
                        ; Should be updated by calling the syntax highlighting proc.
                        ;
  .syn_context  dd  ?   ; Syntax context after the line
                        ;
  .fold_level   dd  ?   ; the level of folding.
                        ;
  .             TText
ends



struct TTextChunk
  .len   dw ?   ; the length of the text chunk in BYTES (in Utf-8 chars???)
  .color db ?   ; the high order nibble encode the background, low order nibble encodes the text color.
  .flags db ?   ; the text formatting flags. reserved. For example underlining, bold, etc...
ends



object TAsmEdit, TScrollWindow

; private fields

  ._lines         dd    ?             ; pointer to TText structure that contains pointers.
  ._line_widths   dd    ?             ; TArray of TCounter for counting the line width. Must be kept in consistency.

  ._pUndo         dd    ?
  ._pRedo         dd    ?

  ._options       dd    ?             ; behaviour bit options: aeoXXXXXX values.

  ._font_height   dd    ?
  ._font_asc      dd    ?
  ._font_desc     dd    ?

  ._font_width    dd    ?

  ._numfont_width dd    ?

  ._left_margin   dd    ?
  ._num_margin    dd    ?

  ._wrap_width    dd    ?       ; the word wrap width for the word wrapped lines.

  ._caret_x       dd    ?       ; the position inside the line, where the caret is located.
  ._caret_y       dd    ?       ; the line number where the caret is located.

  ._selection_x   dd    ?       ; The selection x
  ._selection_y   dd    ?       ; and y

  ._width_ch      dd    ?       ; width and
  ._height_ch     dd    ?       ; height of the window in chars. Should be set on resizing and on font change.

  ._syntax_proc   dd    ?

; parameters

  param .Selection, .GetSelection, .SetSelection
  param .Text,      .GetText,      .SetText

  param .CaretX,    ._caret_x, NONE
  param .CaretY,    ._caret_y, NONE
  param .SelectionX, ._selection_x, NONE
  param .SelectionY, ._selection_y, NONE

; param methods


  method  .GetSelection
  method  .SetSelection, .value

  method  .GetText
  method  .SetText, .value

  method  .ComputeLeftMarginWidth
  method  .ComputeScreenInChars


; common methods

  method .Create, .parent
  method .Destroy

  method .Resize, .newWidth, .newHeight

  method .UpdateScrollBar

  method .UpdateCaretPos


; rendering methods

  method .Paint

  method ._PaintOneLine, .pLineInfo, .LineNum, .yStart, .xFrom, .yFrom, .width, .height

  method .RenderLines, .LineFrom, .LineTo, .CharFrom, .CharTo

; Text navigarion methods

  method .ScrollToCaretY
  method .ScrollToCaretX

  method .CaretLeftRight, .count, .select
  method .CaretUpDown, .count, .select

  method .CaretHome
  method .CaretEnd
  method .CaretScrHome
  method .CaretScrEnd
  method .CaretTxtHome
  method .CaretTxtEnd

; line handling and formatting methods

  method .FixLineWrap, .LineNum


; undo/redo

  method .SaveForUndo, .LineNum
  method .Undo
  method .Redo


; Text editing methods

  method .ToggleWordWrap, .LineNum
  method .InsertNewLine, .LineNum


; OS event handlers

  method .EventFocusIn
  method .EventFocusOut


  method .EventKeyPress, .utf8, .scancode, .kbdState

endobj





method TAsmEdit.EventKeyPress
begin
        pushad

        mov     esi, [.self]

        mov     ecx, [.kbdState]
        and     ecx, maskShift

        mov     eax, [.utf8]

        test    eax, eax
        jz      .no_char

        cmp     eax, $20
        jb      .ctrl_char

        cmp     eax, $7f
        je      .no_char

        jmp     .finish


.ctrl_char:

        cmp     eax, 23         ; Ctrl+W
        je      .toggle_word_wrap

        cmp     eax, 26         ; Ctrl+Z
        je      .do_undo


        jmp     .finish


.do_undo:
        test    ecx, ecx
        jnz     .do_redo

        exec    esi, TAsmEdit:Undo
        jmp     .finish

.do_redo:
        exec    esi, TAsmEdit:Redo
        jmp     .finish


.toggle_word_wrap:

        exec    esi, TAsmEdit:ToggleWordWrap, [esi+TAsmEdit._caret_y]
        mov     [esi+TAsmEdit._caret_y], eax

        exec    esi, TAsmEdit:UpdateCaretPos
        jmp     .finish


.no_char:
        mov     eax, [.scancode]

        cmp     eax, keyLeftNumpad
        je      .left
        cmp     eax, keyLeft
        je      .left

        cmp     eax, keyRightNumpad
        je      .right
        cmp     eax, keyRight
        je      .right

        cmp     eax, keyUpNumpad
        je      .up
        cmp     eax, keyUp
        je      .up

        cmp     eax, keyDownNumpad
        je      .down
        cmp     eax, keyDown
        je      .down

        jmp     .finish

.wordwrap:




.left:
        exec    esi, TAsmEdit:CaretLeftRight, -1, ecx
        jmp     .endmove


.right:
        exec    esi, TAsmEdit:CaretLeftRight, 1, ecx
        jmp     .endmove

.up:
        exec    esi, TAsmEdit:CaretUpDown, -1, ecx
        jmp     .endmove


.down:
        exec    esi, TAsmEdit:CaretUpDown, 1, ecx
        jmp     .endmove


.endmove:
;        exec    esi, TAsmEdit:Refresh

.finish:
        popad
        return
endp




method TAsmEdit.EventFocusIn
begin
        stdcall CaretAttach, [.self]
        stdcall CaretShow, TRUE
        exec    [.self], TAsmEdit:UpdateCaretPos
        return
endp




method TAsmEdit.EventFocusOut
begin
        stdcall CaretShow, FALSE
        stdcall CaretAttach, 0
        return
endp



method TAsmEdit.UpdateCaretPos
begin
        pushad

        mov     esi, [.self]

        mov     eax, [esi+TAsmEdit._caret_x]
        mov     ecx, [esi+TAsmEdit._caret_y]

        get     edx, esi, TAsmEdit:StartX
        sub     eax, edx
        js      .hide_caret

        imul    eax, [esi+TAsmEdit._font_width]
        add     eax, [esi+TAsmEdit._left_margin]

        get     edx, esi, TAsmEdit:StartY
        sub     ecx, edx
        js      .hide_caret

        imul    ecx, [esi+TAsmEdit._font_height]
        add     ecx, [esi+TAsmEdit._font_height]
        sub     ecx, 2

        stdcall CaretChange, eax, ecx, [esi+TAsmEdit._font_width], 2
        stdcall CaretShow, 1

.finish:
        popad
        return

.hide_caret:

        stdcall CaretShow, 0
        jmp     .finish
endp



; This method is the main text rendering engine of the editor.

method TAsmEdit.RenderLines; , .LineFrom, .LineTo, .CharFrom, .CharTo
.client TBounds
.target RECT

.starty dd ?
begin
        pushad

        xor     eax, eax
        mov     edi, [.self]
        cmp     [edi+TAsmEdit._canvas], eax
        je      .finish

        stdcall LockImg, [edi+TAsmEdit._canvas]

; the editor client rectangle

        xor     eax, eax

        mov     ecx, [edi+TAsmEdit._width]

        mov      edx, [edi+TAsmEdit._height_ch]         ;  mov     edx, [edi+TAsmEdit._height]
        imul     edx, [edi+TAsmEdit._font_height]       ; This way in order to not draw partial lines.

        mov     [.client.x], eax
        mov     [.client.y], eax
        mov     [.client.width], ecx
        mov     [.client.height], edx

; target rectangle to render.

        mov     eax, [.LineFrom]
        mov     ecx, [.LineTo]
        inc     ecx
        imul    eax, [edi+TAsmEdit._font_height]
        imul    ecx, [edi+TAsmEdit._font_height]

        mov     [.target.top], eax
        mov     [.target.bottom], ecx

        mov     eax, [.CharFrom]
        mov     ecx, [.CharTo]
        imul    eax, [edi+TAsmEdit._font_width]
        imul    ecx, [edi+TAsmEdit._font_width]
        add     eax, [edi+TAsmEdit._left_margin]
        add     ecx, [edi+TAsmEdit._left_margin]
        mov     [.target.left], eax
        mov     [.target.right], ecx

        lea     eax, [.client]
        lea     ecx, [.target]
        stdcall RectIntersect, eax, eax, ecx
        jc      .finish_unlock                          ; Empty target rectangle: there is no lines to render.

        stdcall RectCopy, ecx, eax      ; .target == .client

        mov     eax, [.client.width]
        mov     ecx, [.client.height]

        sub     eax, [.client.x]
        sub     ecx, [.client.y]

        mov     [.client.width], eax
        mov     [.client.height], ecx

        mov     eax, [.client.y]
        mov     [.starty], eax

; left margin

        test    [esi+TAsmEdit._options], aeoLeftMargin
        jz      .left_margin_ok

        mov     ecx, [esi+TAsmEdit._left_margin]
        dec     ecx

        xor     eax, eax
        stdcall DrawSolidRect, [edi+TAsmEdit._canvas], eax, [.client.y], ecx, [.client.height], [ASM.clLeftMargin]

        inc     eax ; ==1
        stdcall DrawSolidRect, [edi+TAsmEdit._canvas], ecx, [.client.y], eax, [.client.height], [ASM.clLeftMarginEdge]

.left_margin_ok:

        stdcall DrawSolidRect, [edi+TAsmEdit._canvas], [.client.x], [.client.y], [.client.width], [.client.height], [ASM.clBackground]

; draw the wrap line

        mov     edx, [edi+TAsmEdit._wrap_width]
        get     eax, edi, TAsmEdit:StartX
        sub     edx, eax

        cmp     edx, [.CharFrom]
        jb      .wrap_ok
        cmp     edx, [.CharTo]
        ja      .wrap_ok

        imul    edx, [edi+TAsmEdit._font_width]
        add     edx, [edi+TAsmEdit._left_margin]

        stdcall DrawSolidRect, [edi+TAsmEdit._canvas], edx, [.client.y], 1, [.client.height], [ASM.clWrapColumn]

.wrap_ok:

; now process the lines

        mov     esi, [edi+TAsmEdit._lines]
        test    esi, esi
        jz      .finish_unlock

        get     ecx, edi, TAsmEdit:StartY
        add     ecx, [.LineFrom]
        shl     ecx, 2

        cmp     ecx, [esi+TText.TextLen]
        jae     .end_line_loop

.search_not_empty:

        mov     eax, ecx
        pos2ofs eax, esi

        cmp     dword [esi+eax], 0
        jne     .line_loop

        sub     ecx, 4
        mov     eax, [edi+TAsmEdit._font_height]
        sub     [.starty], eax
        jmp     .search_not_empty

.line_loop:

        mov     eax, [.starty]
        cmp     eax, [.target.bottom]
        jge     .end_line_loop

        cmp     ecx, [esi+TText.TextLen]
        jae     .end_line_loop

        mov     eax, ecx
        add     ecx, 4

        pos2ofs eax, esi
        mov     edx, [esi+eax]

        test    edx, edx
        jz      .line_loop

; paint line number

        test    [edi+TAsmEdit._options], aeoLeftMargin
        jz      .do_paint_text

        test    [edi+TAsmEdit._options], aeoLineNumbers
        jz      .do_paint_text

        push    ecx edx

        mov     ecx, [.starty]
        cmp     ecx, [.target.top]
        jl      .line_num_ok

        add     ecx, [edi+TAsmEdit._font_asc]

        stdcall NumToStr, [edx+TLineInfo.line_num], ntsDec or ntsUnsigned
        push    eax

        stdcall StrPtr, eax
        push    eax

        stdcall GetTextBounds, eax, [eax+string.len], [ASM.NumFont]
        sub     eax, [edi+TAsmEdit._num_margin]
        neg     eax
        mov     edx, eax

        pop     eax

        stdcall DrawString, [edi+TAsmEdit._canvas], eax, [eax+string.len], edx, ecx, [ASM.NumFont], [ASM.clLeftMarginText]
        stdcall StrDel ; from the stack

.line_num_ok:
        pop     edx ecx

.do_paint_text:
        push    ebx

        mov     eax, [.CharTo]
        mov     ebx, [.CharFrom]
        sub     eax, ebx
        inc     eax

        exec    edi, TAsmEdit:_PaintOneLine, edx, ecx, [.starty], ebx, 0, eax, 1     ;, .pLineInfo, .LineNum, .yStart, .xFrom, .yFrom, .width, .height
        mov     [.starty], eax

        pop     ebx
        jmp     .line_loop



.end_line_loop:


.finish_unlock:

        stdcall UnlockImg, [edi+TAsmEdit._canvas]

.finish:
        popad
        return
endp



; paint method


method TAsmEdit.Paint
.client TBounds
.start dd ?
;.old_caret dd ?
begin
        pushad

if defined options.DebugMode & options.DebugMode
        stdcall GetFineTimestamp
        mov     [.start], eax
end if

        mov     edi, [.self]
        mov     ebx, [edi+TAsmEdit._canvas]
        test    ebx, ebx
        jz      .finish

        cmp     [Caret.pWindow], edi
        jne     .caret_ok

        stdcall CaretShow, 0

.caret_ok:
        stdcall LockImg, ebx

        xor     eax, eax
        mov     ecx, [edi+TAsmEdit._width]
        mov     edx, [edi+TAsmEdit._height]

        mov     [.client.x], eax
        mov     [.client.y], eax
        mov     [.client.width], ecx
        mov     [.client.height], edx

; left
        test    [esi+TAsmEdit._options], aeoLeftMargin
        jz      .left_margin_ok

        mov     ecx, [esi+TAsmEdit._left_margin]
        sub     [.client.width], ecx
        dec     ecx

        stdcall DrawSolidRect, ebx, [.client.x], [.client.y], ecx, [.client.height], [ASM.clLeftMargin]
        add     [.client.x], ecx

        stdcall DrawSolidRect, ebx, [.client.x], [.client.y], 1, [.client.height], [ASM.clLeftMarginEdge]
        inc     [.client.x]

.left_margin_ok:

        mov     esi, [edi+TAsmEdit._lines]
        test    esi, esi
        jz      .finish

        get     ecx, edi, TAsmEdit:StartY
        shl     ecx, 2

        cmp     ecx, [esi+TText.TextLen]
        jae     .end_line_loop

.search_not_empty:

        mov     eax, ecx
        pos2ofs eax, esi

        cmp     dword [esi+eax], 0
        jne     .line_loop

        sub     ecx, 4
        mov     eax, [edi+TAsmEdit._font_height]
        sub     [.client.y], eax
        jmp     .search_not_empty


.line_loop:

        mov     eax, [.client.y]
        cmp     eax, [edi+TAsmEdit._height]
        jge     .end_line_loop

        cmp     ecx, [esi+TText.TextLen]
        jae     .end_line_loop

        mov     eax, ecx
        add     ecx, 4

        pos2ofs eax, esi
        mov     edx, [esi+eax]

        test    edx, edx
        jz      .line_loop

; paint line number

        test    [edi+TAsmEdit._options], aeoLeftMargin
        jz      .do_paint_text

        test    [edi+TAsmEdit._options], aeoLineNumbers
        jz      .do_paint_text

        push    ebx ecx edx

        mov     ecx, [.client.y]
        test    ecx, ecx
        jns     @f
        xor     ecx, ecx
@@:
        add     ecx, [edi+TAsmEdit._font_asc]

        stdcall NumToStr, [edx+TLineInfo.line_num], ntsDec or ntsUnsigned
        push    eax

        stdcall StrPtr, eax
        push    eax

        stdcall GetTextBounds, eax, [eax+string.len], [ASM.NumFont]
        sub     eax, [edi+TAsmEdit._num_margin]
        neg     eax
        mov     edx, eax

        pop     eax

        stdcall DrawString, ebx, eax, [eax+string.len], edx, ecx, [ASM.NumFont], [ASM.clLeftMarginText]
        stdcall StrDel ; from the stack

        pop     edx ecx ebx

.do_paint_text:
        exec    edi, TAsmEdit:_PaintOneLine, edx, ecx, [.client.y], 0, 0, [edi+TAsmEdit._width_ch], [edi+TAsmEdit._height_ch]
        mov     [.client.y], eax
        jmp     .line_loop


.end_line_loop:

        stdcall UnlockImg, ebx

        cmp     [Caret.pWindow], edi
        jne     .finish

        exec    edi, TAsmEdit:UpdateCaretPos

.finish:


if defined options.DebugMode & options.DebugMode
        stdcall GetFineTimestamp
        sub     eax, [.start]

        OutputValue "Rendering time [us]:", eax, 10, -1
end if

        popad
        return
endp




method TAsmEdit.UpdateScrollBar
begin
        pushad
        mov     esi, [.self]

        mov     ecx, [esi+TWindow._width]
        mov     edx, [esi+TWindow._height]

        mov     eax, ecx
        mov     ebx, edx

        sub     eax, [GUI.scrollWidth]          ; width - scrollWidth
        sub     ebx, [GUI.scrollWidth]          ; height - scrollWidth

        set     [esi+TAsmEdit.YScroller], TScrollbar:x, eax
        set     [esi+TAsmEdit.YScroller], TScrollbar:y, 0
        set     [esi+TAsmEdit.YScroller], TScrollbar:height, edx
        set     [esi+TAsmEdit.YScroller], TScrollbar:width, [GUI.scrollWidth]

        set     [esi+TAsmEdit.XScroller], TScrollbar:x, 0
        set     [esi+TAsmEdit.XScroller], TScrollbar:y, ebx
        set     [esi+TAsmEdit.XScroller], TScrollbar:width, ecx
        set     [esi+TAsmEdit.XScroller], TScrollbar:height, [GUI.scrollWidth]

        xor     eax, eax
        cmp     [esi+TAsmEdit._font_width], eax
        lea     eax, [eax+1]
        je      .xpage_ok

        mov     eax, [esi+TAsmEdit._width]
        sub     eax, [esi+TAsmEdit._left_margin]
        cdq
        idiv    [esi+TAsmEdit._font_width]
        test    eax, eax
        jnz     .xpage_ok

        inc     eax

.xpage_ok:

        set     [esi+TAsmEdit.XScroller], TScrollbar:Page, eax

        mov     ecx, [esi+TAsmEdit._line_widths]
        mov     edx, [ecx+TArray.count]
        test    edx, edx
        jz      .xsize_ok

        mov     edx, [ecx+TArray.array+sizeof.TCounter*edx+TCounter.value- sizeof.TCounter]
        sub     edx, eax

.xsize_ok:

        set     [esi+TAsmEdit.XScroller], TScrollbar:Max, edx

; set Y scrollers page and

        xor     eax, eax
        cmp     [esi+TAsmEdit._font_height], eax
        lea     eax, [eax+1]
        je      .ypage_ok

        mov     eax, [esi+TAsmEdit._height]
        cdq
        idiv    [esi+TAsmEdit._font_height]
        test    eax, eax
        jnz     .ypage_ok

        inc     eax

.ypage_ok:

        set     [esi+TAsmEdit.YScroller], TScrollbar:Page, eax

        mov     edx, [esi+TAsmEdit._lines]
        mov     edx, [edx+TText.TextLen]
        shr     edx, 2
        sub     edx, eax

        set     [esi+TAsmEdit.YScroller], TScrollbar:Max, edx

        popad
        return
endp



; general methods


method TAsmEdit.Create
begin
        inherited [.parent]

        pushad

        mov     edi, [.self]

        mov     [edi+TAsmEdit._cursor], mcText
        mov     [edi+TAsmEdit.__want_focus], TRUE

        mov     [edi+TAsmEdit._options], aeoNavRightAfterEOL or aeoLeftMargin or aeoLineNumbers or aeoStripedBackground ;or aeoScrollYMore or aeoScrollXMore

        stdcall GetFontMetrics, [ASM.Font]

        mov     [edi+TAsmEdit._font_height], eax
        mov     [edi+TAsmEdit._font_asc], ebx
        mov     [edi+TAsmEdit._font_desc], edx

        OutputValue "   Font height:", eax, 10, -1
        OutputValue " Font ascender:", ebx, 10, -1
        OutputValue "Font descender:", edx, 10, -1

        stdcall GetTextBounds, "ABCDEFGH", 8, [ASM.Font]
        sar     eax, 3
        mov     [edi+TAsmEdit._font_width], eax

        OutputValue "    Font width:", eax, 10, -1

        stdcall GetTextBounds, "99999999", 8, [ASM.NumFont]
        sar     eax, 3
        mov     [edi+TAsmEdit._numfont_width], eax

        mov     [edi+TAsmEdit._wrap_width], 80

        mov     [edi+TAsmEdit._syntax_proc], SyntaxFASM


; test only!

        set     edi, TAsmEdit:Text, utf8_long    ; 0   ; set the needed text structures, but empty.
        mov     [edi+TAsmEdit._caret_x], 4
        mov     [edi+TAsmEdit._caret_y], 4

        popad
        return
endp




method TAsmEdit.Destroy
begin
        push    ecx
        mov     ecx, [.self]

        stdcall TextFree, [ecx+TAsmEdit._lines]

        pop     ecx
        inherited
        return
endp



method TAsmEdit.Resize
begin
        inherited [.newWidth], [.newHeight]
        exec    [.self], TAsmEdit:ComputeScreenInChars
        exec    [.self], TAsmEdit:Refresh
        return
endp



; parameters get/set methods


method TAsmEdit.GetSelection
begin

        return
endp




method TAsmEdit.SetSelection
begin

        return
endp




; returns two numbers:
;   eax - the whole left margin width
;   edx - the number part of the left margin.


method  TAsmEdit.ComputeLeftMarginWidth
begin
        pushad

        mov     esi, [.self]
        xor     ebx, ebx

        mov     [esi+TAsmEdit._num_margin], ebx

        test    [esi+TAsmEdit._options], aeoLeftMargin
        jz      .marginok

        test    [esi+TAsmEdit._options], aeoLineNumbers
        jz      .marginok1

        mov     eax, [esi+TAsmEdit._lines]
        mov     eax, [eax+TText.TextLen]
        shr     eax, 2                          ; the number of the lines.
        test    eax, eax
        jz      .marginok1

        call    .digits
        imul    ecx, [esi+TAsmEdit._numfont_width]
        add     ebx, ecx

        mov     [esi+TAsmEdit._num_margin], ebx

.marginok1:

        mov     eax, [ASM.iconBreakpoints]
        add     ebx, [eax+TImage.width]

        mov     eax, [ASM.xspcIconBrkPnts]
        lea     ebx, [ebx+2*eax]           ; left/right margins to the image.

        mov     eax, [ASM.iconFoldings]
        add     ebx, [eax+TImage.width]

        add     ebx, 3+1                   ; 3px for word wrap cut + 1px whitespace at the line beginning.

.marginok:
        xchg    [esi+TAsmEdit._left_margin], ebx
        cmp     ebx, [esi+TAsmEdit._left_margin]
        je      .finish

;        exec    [.self], TAsmEdit:UpdateCaretPosition
        exec    [.self], TAsmEdit:ComputeScreenInChars

.finish:
        popad
        return


.digits:
        xor     ecx, ecx
        cmp     eax, 1          ; 0
        jb      .edig

        inc     ecx
        cmp     eax, 10         ; 1
        jb      .edig

        inc     ecx
        cmp     eax, 100        ; 2
        jb      .edig

        inc     ecx
        cmp     eax, 1000       ; 3
        jb      .edig

        inc     ecx
        cmp     eax, 10000      ; 4
        jb      .edig

        inc     ecx
        cmp     eax, 100000     ; 5
        jb      .edig

        inc     ecx
        cmp     eax, 1000000    ; 6
        jb      .edig

        inc     ecx
        cmp     eax, 10000000   ; 7
        jb      .edig

        inc     ecx
        cmp     eax, 100000000  ; 8
        jb      .edig

        inc     ecx
        cmp     eax, 1000000000 ; 9
        jb      .edig

        inc     ecx             ; 10

.edig:
        retn

endp





method TAsmEdit.ComputeScreenInChars
begin
        pushad
        mov     esi, [.self]

; width
        mov     eax, [esi+TAsmEdit._width]
        sub     eax, [esi+TAsmEdit._left_margin]
        cdq

        idiv    [esi+TAsmEdit._font_width]
        test    edx, edx
        jz      @f
        inc     eax
@@:
        mov     [esi+TAsmEdit._width_ch], eax

        OutputValue "Screen width:", eax, 10, -1

; height
        mov     eax, [esi+TAsmEdit._height]
        cdq

        idiv    [esi+TAsmEdit._font_height]
        test    edx, edx
        jz      @f
        inc     eax
@@:
        mov     [esi+TAsmEdit._height_ch], eax

        OutputValue "Screen height:", eax, 10, -1

        popad



        return
endp





method TAsmEdit.GetText
begin

        return
endp




method TAsmEdit.SetText
begin
        pushad

        mov     ebx, [.self]

; free the previous TText structure for the TAsmEdit._lines

        mov     esi, [ebx+TAsmEdit._lines]
        test    esi, esi
        jz      .create_new_lines

        xor     ecx, ecx

.free_loop:
        cmp     ecx, [esi+TText.TextLen]
        jae     .end_free_loop

        mov     eax, ecx
        pos2ofs eax, esi

        stdcall TextFree, [esi+eax]
        add     ecx, 4
        jmp     .free_loop

.end_free_loop:

        stdcall TextFree, esi

.create_new_lines:

        stdcall TextCreate, sizeof.TText
        mov     [ebx+TAsmEdit._lines], eax

; new line widths array.

        stdcall FreeMem, [ebx+TAsmEdit._line_widths]
        stdcall CreateArray, sizeof.TCounter
        mov     [ebx+TAsmEdit._line_widths], eax

        mov     edi, [.value]
        test    edi, edi
        jz      .finish

.text_loop:

        cmp     byte [edi], 0
        je      .end_text

        exec    ebx, TAsmEdit:InsertNewLine, -1
        mov     esi, eax

        xor     ecx, ecx

.line_loop:
        stdcall DecodeUtf8, [edi]
        add     edi, edx

        test    eax, eax
        jz      .end_text

        cmp     eax, $0d
        je      .new_line
        cmp     eax, $0a
        je      .new_line

        stdcall EncodeUtf8, eax
        jc      .end_text

        stdcall TextAddChar, [esi], eax
        mov     [esi], edx
        jc      .end_text

        inc     ecx
        jmp     .line_loop

.new_line:
        xor     eax, $0d xor $0a
        cmp     [edi], al
        jne     .cr_ok
        inc     edi

.cr_ok:
        or      eax, -1

        mov     edx, [esi]
        test    [edx+TLineInfo.flags], aelfWordWrap
        cmovnz  eax, [ebx+TAsmEdit._wrap_width]

        stdcall __ComputeWrapLines, [esi], eax
        push    eax

        mov     [edx+TLineInfo.line_width], ecx
        stdcall IncCount, [ebx+TAsmEdit._line_widths], ecx
        mov     [ebx+TAsmEdit._line_widths], edx

        pop     ecx
        test    ecx, ecx
        jz      .text_loop

        mov     edx, [ebx+TAsmEdit._lines]

        lea     eax, [4*ecx]

        stdcall TextSetGapSize, edx, eax
        stdcall TextMoveGap, edx, -1
        mov     [ebx+TAsmEdit._lines], edx

        push    edi

        mov     edi, edx
        add     edi, [edx+TText.GapStart]

        add     [edx+TText.GapStart], eax
        add     [edx+TText.TextLen],  eax
        add     [edx+TText.Caret],    eax
        sub     [edx+TText.GapLen],   eax

        xor     eax, eax
        rep stosd

        pop     edi

        jmp     .text_loop


.end_text:

        exec    ebx, TAsmEdit:ComputeLeftMarginWidth
        exec    ebx, TAsmEdit:UpdateScrollBar


.finish:

        popad
        return
endp





; Navigation manipulation methods.



method TAsmEdit.ScrollToCaretX
.max_col   dd ? ; the maximal line NOT including partial rows.
.half_x    dd ?
.quarter_x dd ?
begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TAsmEdit._lines]

        mov     eax, [esi+TAsmEdit._width]
        sub     eax, [esi+TAsmEdit._left_margin]
        xor     edx, edx
        div     [esi+TAsmEdit._font_width]

        dec     eax
        mov     [.max_col], eax

        shr     eax, 1
        mov     [.half_x], eax

        shr     eax, 1
        mov     [.quarter_x], eax


        stdcall CaretShow, 0

        mov     eax, [esi+TAsmEdit._caret_x]
        sub     eax, [esi+TAsmEdit._start_x]    ; offset from the screen home.
        js      .scroll_right

; scroll left

        sub     eax, [.max_col]
        jb      .finish

        test    [esi+TAsmEdit._options], aeoScrollXMore
        jz      .morex_ok

        add     eax, [.quarter_x]

.morex_ok:

        mov     edx, [esi+TAsmEdit._start_x]
        add     edx, eax

        mov     [esi+TAsmEdit._start_x], edx
        set     [esi+TAsmEdit.XScroller], TScrollbar:Pos, edx

        cmp     eax, [.half_x]
        ja      .paint_all

; now scroll the screen left

        mov     edx, eax
        imul    edx, [esi+TAsmEdit._font_width]

        mov     ecx, [esi+TAsmEdit._width_ch]
        imul    ecx, [esi+TAsmEdit._font_width]

        stdcall ScrollRectLeft, [esi+TAsmEdit._canvas], [esi+TAsmEdit._left_margin], 0, ecx, [esi+TAsmEdit._height], edx

        mov     edx, [esi+TAsmEdit._width_ch]
        dec     edx

        sub     eax, edx
        neg     eax

        OutputValue "Scroll left from: ", eax, 10, -1
        OutputValue "Scroll left to:   ", edx, 10, -1

        exec    esi, TAsmEdit:RenderLines, 0, [esi+TAsmEdit._height_ch], eax, edx,   ; .LineFrom, .LineTo, .CharFrom, .CharTo

.changed:
        exec    esi, TAsmEdit:RectChanged, 0, FALSE

.finish:
        exec    esi, TAsmEdit:UpdateCaretPos
        stdcall CaretShow, 1

        popad
        return


.paint_all:
        exec    esi, TAsmEdit:Paint
        jmp     .changed



.scroll_right:

        neg     eax

        test    [esi+TAsmEdit._options], aeoScrollXMore
        jz      .morex_ok2

        add     eax, [.quarter_x]

.morex_ok2:

        xor     ecx, ecx
        mov     edx, [esi+TAsmEdit._start_x]
        sub     edx, eax
        cmovs   edx, ecx

        mov     [esi+TAsmEdit._start_x], edx
        set     [esi+TAsmEdit.XScroller], TScrollbar:Pos, edx

        cmp     eax, [.half_x]
        ja      .paint_all

        mov     edx, eax
        imul    edx, [esi+TAsmEdit._font_width]

        mov     ecx, [esi+TAsmEdit._width_ch]
        imul    ecx, [esi+TAsmEdit._font_width]

        stdcall ScrollRectRight, [esi+TAsmEdit._canvas], [esi+TAsmEdit._left_margin], 0, ecx, [esi+TAsmEdit._height], edx

        exec    esi, TAsmEdit:RenderLines, 0, [esi+TAsmEdit._height_ch], 0, eax   ; .LineFrom, .LineTo, .CharFrom, .CharTo

        jmp     .changed

endp







method TAsmEdit.ScrollToCaretY
.max_line  dd ? ; the maximal line NOT including partial rows.
.half_y    dd ?
.quarter_y dd ?
begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TAsmEdit._lines]

        mov     eax, [esi+TAsmEdit._height]
        xor     edx, edx
        div     [esi+TAsmEdit._font_height]

        dec     eax
        mov     [.max_line], eax

        shr     eax, 1
        mov     [.half_y], eax

        shr     eax, 1
        mov     [.quarter_y], eax


        stdcall CaretShow, 0

        mov     eax, [esi+TAsmEdit._caret_y]
        sub     eax, [esi+TAsmEdit._start_y]    ; offset from the screen home.
        js      .scroll_down

; scroll up

        sub     eax, [.max_line]
        jb      .finish

        test    [esi+TAsmEdit._options], aeoScrollYMore
        jz      .morey_ok

        add     eax, [.quarter_y]

.morey_ok:
        xor     ecx, ecx

        test    [esi+TAsmEdit._options], aeoStripedBackground
        jz      .even_scroll_up_ok

; scroll always even count lines in Zebra mode.
        bt      eax, ecx
        adc     eax, ecx

.even_scroll_up_ok:
        mov     edx, [esi+TAsmEdit._start_y]
        add     edx, eax

        mov     [esi+TAsmEdit._start_y], edx
        set     [esi+TAsmEdit.YScroller], TScrollbar:Pos, edx

        cmp     eax, [.half_y]
        ja      .paint_all

; now scroll the screen up

        mov     edx, eax
        imul    edx, [esi+TAsmEdit._font_height]

        mov     ecx, [esi+TAsmEdit._height_ch]
        imul    ecx, [esi+TAsmEdit._font_height]

        stdcall ScrollRectUp, [esi+TAsmEdit._canvas], 0, 0, [esi+TAsmEdit._width], ecx, edx

        mov     edx, [esi+TAsmEdit._height_ch]
        dec     edx

        sub     eax, edx
        neg     eax

        exec    esi, TAsmEdit:RenderLines, eax, edx, 0, [esi+TAsmEdit._width_ch]    ; .LineFrom, .LineTo, .CharFrom, .CharTo

.changed:
        exec    esi, TAsmEdit:RectChanged, 0, FALSE

.finish:
        exec    esi, TAsmEdit:UpdateCaretPos
        stdcall CaretShow, 1

        popad
        return


.paint_all:
        exec    esi, TAsmEdit:Paint
        jmp     .changed



.scroll_down:

        neg     eax

        test    [esi+TAsmEdit._options], aeoScrollYMore
        jz      .morey_ok2

        add     eax, [.quarter_y]

.morey_ok2:
        xor     ecx, ecx

        test    [esi+TAsmEdit._options], aeoStripedBackground
        jz      .even_scroll_dn_ok

; scroll always even count lines in Zebra mode.
        bt      eax, ecx
        adc     eax, ecx

.even_scroll_dn_ok:

        mov     edx, [esi+TAsmEdit._start_y]
        sub     edx, eax
        cmovs   edx, ecx

        mov     [esi+TAsmEdit._start_y], edx
        set     [esi+TAsmEdit.YScroller], TScrollbar:Pos, edx

        cmp     eax, [.half_y]
        ja      .paint_all

        mov     edx, eax
        imul    edx, [esi+TAsmEdit._font_height]

        mov     ecx, [esi+TAsmEdit._height_ch]
        imul    ecx, [esi+TAsmEdit._font_height]

        stdcall ScrollRectDn, [esi+TAsmEdit._canvas], 0, 0, [esi+TAsmEdit._width], ecx, edx

        exec    esi, TAsmEdit:RenderLines, 0, eax, 0, [esi+TAsmEdit._width_ch]    ; .LineFrom, .LineTo, .CharFrom, .CharTo

        jmp     .changed

endp









method TAsmEdit.CaretLeftRight
begin
        pushad

; debug only!
        stdcall GetFineTimestamp
        push    eax
; debug only end!

        mov     esi, [.self]

        xor     edx, edx                    ; minimal caret pos 0

        mov     ebx, [esi+TAsmEdit._caret_x]
        add     ebx, [.count]
        cmovs   ebx, edx

        mov     [esi+TAsmEdit._caret_x], ebx
        cmp     [.select], 0
        jne     .select_ok

        mov     [esi+TAsmEdit._selection_x], ebx

.select_ok:
        exec    esi, TAsmEdit:ScrollToCaretX

; debug only!
        stdcall GetFineTimestamp
        sub     eax, [esp]

        OutputValue "Scroll left/right time [us]:", eax, 10, -1

        add     esp, 4
; end debug only!

        popad
        return
endp





method TAsmEdit.CaretUpDown
begin
        pushad

; debug only!
        stdcall GetFineTimestamp
        push    eax
; debug only end!

        mov     esi, [.self]

        xor     edx, edx                    ; minimal caret pos 0

        mov     ecx, [esi+TAsmEdit._lines]
        mov     ecx, [ecx+TText.TextLen]
        shr     ecx, 2  ; line count        ; maximal caret pos

        mov     ebx, [esi+TAsmEdit._caret_y]
        add     ebx, [.count]

        cmovs   ebx, edx

        cmp     ebx, ecx
        cmova   ebx, ecx

        mov     [esi+TAsmEdit._caret_y], ebx

        cmp     [.select], 0
        jne     .select_ok

        mov     [esi+TAsmEdit._selection_y], ebx

.select_ok:

        exec    esi, TAsmEdit:ScrollToCaretY

; debug only!
        stdcall GetFineTimestamp
        sub     eax, [esp]

        OutputValue "Scroll up/down time [us]:", eax, 10, -1

        add     esp, 4
; end debug only!

        popad
        return
endp



method TAsmEdit.CaretHome
begin

        return
endp


method TAsmEdit.CaretEnd
begin

        return
endp


method TAsmEdit.CaretScrHome
begin

        return
endp


method TAsmEdit.CaretScrEnd
begin

        return
endp


method TAsmEdit.CaretTxtHome
begin

        return
endp


method TAsmEdit.CaretTxtEnd
begin

        return
endp


; Line handling and formatting methods.



; Updates the empty placeholder lines on the wrapped lines.

method TAsmEdit.FixLineWrap ; .LineNum

.total dd ?

begin
        pushad

        mov     esi, [.self]
        mov     edi, [esi+TAsmEdit._lines]
        test    edi, edi
        jz      .finish         ; no text lines

        mov     eax, [edi+TText.TextLen]
        add     eax, [edi+TText.GapLen]
        mov     [.total], eax

        mov     ebx, [.LineNum]
        shl     ebx, 2                  ; byte offset.

        cmp     ebx, [edi+TText.TextLen]
        jae     .finish

        add     ebx, 4

;1. search back for valid line.

.loop_back:
        sub     ebx, 4
        js      .finish         ; not found

        mov     ecx, ebx
        pos2ofs ecx, edi

        cmp     dword [edi+ecx], 0
        je      .loop_back

;2. Move the gap just after the line.

        lea     ecx, [ebx+4]
        stdcall TextMoveGap, edi, ecx

;3. First remove all zeroes after that.

        add     ecx, [edi+TText.GapLen]         ; now ecx points just after the gap.

.del_loop:

        cmp     ecx, [.total]
        jae     .end_del

        cmp     dword [edi+ecx], 0
        jne     .end_del

        add     ecx, 4
        add     [edi+TText.GapLen], 4
        sub     [edi+TText.TextLen], 4
        jmp     .del_loop

;4. Now compute the count of the NULL lines and fix the line width.

.end_del:

        or      eax, -1
        mov     ebx, [edi+ebx]  ; the line info. It is <> NULL.

        test    [ebx+TLineInfo.flags], aelfWordWrap
        cmovnz  eax, [esi+TAsmEdit._wrap_width]

        stdcall __ComputeWrapLines, ebx, eax

        push    eax

        cmp     ecx, [ebx+TLineInfo.line_width]
        je      .line_width_ok

; change the line width of the line.

        stdcall DecCount, [esi+TAsmEdit._line_widths], [ebx+TLineInfo.line_width]       ; old line width
        mov     [ebx+TLineInfo.line_width], ecx                                         ; new line width
        stdcall IncCount, edx, ecx
        mov     [esi+TAsmEdit._line_widths], edx

.line_width_ok:

        pop     ecx
        jecxz   .finish

        lea     eax, [4*ecx]    ; in bytes

        stdcall TextSetGapSize, edi, eax
        mov     [esi+TAsmEdit._lines], edx

        mov     edi, edx
        add     edi, [edx+TText.GapStart]       ; clear from here

        add     [edx+TText.GapStart], eax
        add     [edx+TText.TextLen], eax
        sub     [edx+TText.GapLen], eax

        xor     eax, eax
        rep stosd

.finish:
        popad
        return
endp


; Undo/Redo methods


method TAsmEdit.SaveForUndo
begin
        pushad

        mov     esi, [.self]
        mov     edx, [esi+TAsmEdit._pUndo]
        mov     ebx, [.LineNum]

;        test    edx, edx
;        jz      .add_new

;        cmp     [edx+TUndoInfo.line], ebx
;        je      .finish                                         ; the same line is already saved in the previous record.

; add new record in the chain.

.add_new:
        stdcall GetMem, sizeof.TUndoInfo
        jc      .finish

        mov     edi, eax

        mov     [edi+TUndoInfo.pNext], edx
        mov     [esi+TAsmEdit._pUndo], edi

        mov     ecx, [esi+TAsmEdit._caret_x]
        mov     edx, [esi+TAsmEdit._caret_y]

        mov     [edi+TUndoInfo.line], ebx
        mov     [edi+TUndoInfo.caret_x], ecx
        mov     [edi+TUndoInfo.caret_y], edx

        mov     edx, [esi+TAsmEdit._lines]
        shl     ebx, 2

        pos2ofs ebx, edx
        stdcall TextDup, [edx+ebx]

        mov     [edi+TUndoInfo.pText], eax

; now free all redo records, because every new stored undo point invalidates the whole redo list.

.clean_redo:

        mov     ecx, [esi+TAsmEdit._pRedo]
        and     [esi+TAsmEdit._pRedo], 0

.clean_loop:
        jecxz      .finish

        stdcall TextFree, [ecx+TUndoInfo.pText]

        push    ecx
        mov     ecx, [ecx+TUndoInfo.pNext]

        stdcall FreeMem ; from the stack
        jmp     .clean_loop

.finish:
        popad
        return
endp



method TAsmEdit.Undo
begin
        pushad

        mov     esi, [.self]
        mov     ebx, [esi+TAsmEdit._lines]

        mov     edi, [esi+TAsmEdit._pUndo]

        test    edi, edi
        jz      .finish         ; nothing to undo.

        mov     edx, [edi+TUndoInfo.pNext]
        mov     [esi+TAsmEdit._pUndo], edx           ; remove from the UNDO list.

        mov     edx, [esi+TAsmEdit._pRedo]
        mov     [edi+TUndoInfo.pNext], edx
        mov     [esi+TAsmEdit._pRedo], edi           ; insert in the REDO list.

; from here is common for undo and redo

.common_undo_redo:

        push    [edi+TUndoInfo.caret_x] [esi+TAsmEdit._caret_x]
        pop     [edi+TUndoInfo.caret_x] [esi+TAsmEdit._caret_x] ; xchg caret_x

        push    [edi+TUndoInfo.caret_y] [esi+TAsmEdit._caret_y]
        pop     [edi+TUndoInfo.caret_y] [esi+TAsmEdit._caret_y] ; xchg caret_y

        mov     eax, [edi+TUndoInfo.line]
        shl     eax, 2          ; in bytes
        pos2ofs eax, ebx

        pushd   [edi+TUndoInfo.pText] [ebx+eax]
        popd    [edi+TUndoInfo.pText] [ebx+eax]      ; xchg TLineInfo structures of the line.

; fix the horizontal scrollbar size.

        mov     ecx, [ebx+eax]
        stdcall IncCount, [esi+TAsmEdit._line_widths], [ecx+TLineInfo.line_width]
        mov     [esi+TAsmEdit._line_widths], edx

        mov     ecx, [edi+TUndoInfo.pText]
        stdcall DecCount, [esi+TAsmEdit._line_widths], [ecx+TLineInfo.line_width]
        mov     [esi+TAsmEdit._line_widths], edx

; fix the NULL lines of wrapped lines.

        exec    esi, TAsmEdit:FixLineWrap, [edi+TUndoInfo.line]

        exec    esi, TAsmEdit:ScrollToCaretY
        exec    esi, TAsmEdit:UpdateScrollBar
        exec    esi, TAsmEdit:Refresh

.finish:
        popad
        return
endp



method TAsmEdit.Redo
begin
        pushad

        mov     esi, [.self]
        mov     ebx, [esi+TAsmEdit._lines]

        mov     edi, [esi+TAsmEdit._pRedo]

        test    edi, edi
        jz      methods.TAsmEdit.Undo.finish

        mov     edx, [edi+TUndoInfo.pNext]
        mov     [esi+TAsmEdit._pRedo], edx           ; remove from the REDO list.

        mov     edx, [esi+TAsmEdit._pUndo]
        mov     [edi+TUndoInfo.pNext], edx
        mov     [esi+TAsmEdit._pUndo], edi           ; insert in the UNDO list.

        jmp     methods.TAsmEdit.Undo.common_undo_redo
endp



; Text editing methods




method TAsmEdit.ToggleWordWrap      ;, .LineNum
begin
        pushad

        mov     ecx, [.LineNum]
        mov     [esp+4*regEAX], ecx     ; return LineNum

        mov     esi, [.self]
        mov     edi, [esi+TAsmEdit._lines]

        test    edi, edi
        jz      .finish

        inc     ecx

.back_loop:
        lea     eax, [4*ecx]
        stdcall TextMoveGap, edi, eax

        dec     ecx
        js      .finish

        mov     ebx, [edi+4*ecx]
        test    ebx, ebx
        jz      .back_loop

; ebx pointer to TLineInfo

        mov     [esp+4*regEAX], ecx     ; return the new LineNum

        exec    esi, TAsmEdit:SaveForUndo, ecx

        xor     [ebx+TLineInfo.flags], aelfWordWrap             ; change the flag.

        exec    esi, TAsmEdit:FixLineWrap, ecx

        exec    esi, TAsmEdit:UpdateScrollBar
        exec    esi, TAsmEdit:Refresh

.finish:
        popad
        return
endp






method TAsmEdit.InsertNewLine   ;, .LineNum
begin
        pushad

        mov     esi, [.self]
        mov     edx, [esi+TAsmEdit._lines]

        stdcall TextSetGapSize, edx, 4

        mov     eax, [.LineNum]
        shl     eax, 4

        stdcall TextMoveGap, edx, eax

        mov     ebx, [edx+TText.GapStart]

        stdcall TextCreate, sizeof.TLineInfo
        mov     [edx+ebx], eax
        lea     ebx, [edx+ebx]

        mov     [esp+4*regEAX], ebx                     ; return value in EAX is the pointer to the new inserted TText.

        mov     [eax+TLineInfo.flags], aelfWordWrap

        add     [edx+TText.GapStart], 4
        add     [edx+TText.TextLen],  4
        add     [edx+TText.Caret],    4
        sub     [edx+TText.GapLen],   4

        call    .get_line_number
        mov     [eax+TLineInfo.line_num], ecx

        mov     [esi+TAsmEdit._lines], edx

;        stdcall IncCount, [esi+TAsmEdit._line_widths], 0
;        mov     [esi+TAsmEdit._line_widths], edx

        popad
        return


.get_line_number:
        push    ebx
        xor     ecx, ecx

.loop:
        cmp     ebx, edx
        je      .number_ok

        sub     ebx, 4
        cmp     [ebx], ecx
        je      .loop

        mov     ecx, [ebx]
        mov     ecx, [ecx+TLineInfo.line_num]

.number_ok:
        inc     ecx

        pop     ebx
        push    ecx

        add     ebx, [edx+TText.GapLen]

.inc_loop:
        add     ebx, 4
        cmp     ebx, [edx+TText.TextLen]
        jae     .finish

        mov     ecx, [edx+ebx]
        jecxz   .inc_loop

        inc     [ecx+TLineInfo.line_num]
        jmp     .inc_loop

.finish:
        pop     ecx
        retn
endp






;method TAsmEdit._PaintLineBg   ;, .LineNum, .yStart, .xFrom, .yFrom, .width, .height
;begin
;        pushad
;
;        mov     esi, [.self]
;        cmp     [esi+TAsmEdit._canvas], 0
;        je      .finish
;
;        mov     eax, [.xFrom]
;        mov     edx, [.width]
;        mov     ecx, [esi+TAsmEdit._font_width]
;
;        imul    eax, ecx
;        imul    edx, ecx
;
;        test    [esi+TAsmEdit._options], aeoLeftMargin
;        jz      .lmok
;        add     eax, [esi+TAsmEdit._left_margin]
;.lmok:
;
;        mov     [.xFrom], eax
;        mov     [.width], edx
;
;        mov     ecx, [esi+TAsmEdit._font_height]
;        mov     edx, [.yStart]
;
;        mov     ebx, [.LineNum]
;        and     ebx, 4
;
;        mov     eax, [.height]
;
;.loop:
;        dec     eax
;        js      .draw_wrap_line
;
;        stdcall DrawSolidRect, [esi+TAsmEdit._canvas], [.xFrom], edx, [.width], ecx, [ASM.clBackground+ebx]
;        add     edx, ecx
;
;        test    [esi+TAsmEdit._options], aeoStripedBackground
;        jz      .loop
;
;        xor     ebx, 4
;        jmp     .loop
;
;.draw_wrap_line:
;
;        mov     edx, [esi+TAsmEdit._wrap_width]
;        get     eax, esi, TAsmEdit:StartX
;        sub     edx, eax
;        imul    edx, [esi+TAsmEdit._font_width]
;
;        test    [esi+TAsmEdit._options], aeoLeftMargin
;        jz      .lmok2
;        add     edx, [esi+TAsmEdit._left_margin]
;.lmok2:
;
;        mov     eax, [.xFrom]
;        cmp     edx, eax
;        jl      .finish
;
;        add     eax, [.width]
;        cmp     edx, eax
;        jge     .finish
;
;        mov     ecx, [.height]
;        imul    ecx, [esi+TAsmEdit._font_height]
;
;        stdcall DrawSolidRect, [esi+TAsmEdit._canvas], edx, [.yStart], 1, ecx, [ASM.clWrapColumn]
;
;.finish:
;        popad
;        return
;endp



;
; Paints one line on the editor canvas.
;
; .pLineInfo points to the TLineInfo structure ( TText descendent )
; .yStart is the Y coordinate of the start of the line. It can be negative for the word wrapped lines, displayed as a first line and partially visible.
; .xFrom, .yFrom - char units
; .width, height - char units
;
; returns:
;    EAX - pointer to the next TLineInfo block.
;

method TAsmEdit._PaintOneLine   ;, .pLineInfo, .LineNum, .yStart, .xFrom, .yFrom, .width, .height

.skip        dd ?       ; how many characters to skip from the begining of the line.
.max_char    dd ?       ; what is the maximal char to render

.font_width  dd ?
.font_height dd ?
.font_asc    dd ?
.chunk_cnt   dd ?

.wrap_width  dd ?

.i_chunk     dd ?
.chunk_len   dd ?
.chunk_color dd ?
.chunk_bkgnd dd ?

.bytes       dd ?

.start_out   dd ?
.len_out     dd ?

.last_space  dd ?
.wrap_pos    dd ?
.wrap_bytes  dd ?

.x           dd ?
.startx      dd ?
.width_px    dd ?

.bg1    dd ?
.bg2    dd ?

begin
        pushad

        and     [.font_height], 0

        mov     esi, [.self]
        mov     ebx, [.pLineInfo]
        mov     edi, ebx

        test    ebx, ebx
        jz      .finish

        mov     ecx, [esi+TAsmEdit._options]
        and     ecx, aeoStripedBackground
        shr     ecx, 3

        mov     edx, [.LineNum]
        and     edx, 4

        mov     eax, edx
        and     eax, ecx

        mov     eax, [ASM.clBackground+eax]
        mov     [.bg1], eax

        mov     eax, edx
        xor     eax, 4
        and     eax, ecx

        mov     eax, [ASM.clBackground+eax]
        mov     [.bg2], eax

        get     eax, esi, TAsmEdit:StartX

        add     eax, [.xFrom]
        mov     [.skip], eax

        add     eax, [.width]
        mov     [.max_char], eax


        mov     eax, [esi+TAsmEdit._font_height]
        mov     edx, [esi+TAsmEdit._font_width]
        mov     [.font_height], eax
        mov     [.font_width], edx

        mov     eax, [esi+TAsmEdit._font_asc]
        mov     [.font_asc], eax

        mov     eax, [esi+TAsmEdit._wrap_width]
        mov     edx, [esi+TAsmEdit._left_margin]
        mov     [.wrap_width], eax

        mov     eax, [.xFrom]
        imul    eax, [.font_width]
        add     edx, eax

        mov     [.x], edx
        mov     [.startx], edx

        mov     eax, [.width]
        imul    eax, [.font_width]
        mov     [.width_px], eax

        xor     eax, eax
        mov     esi, [ebx+TLineInfo.syn_array]
        test    esi, esi
        jz      .chunk_cnt_ok

        mov     eax, [esi+TArray.count]

.chunk_cnt_ok:

        mov     [.chunk_cnt], eax

        xor     ecx, ecx                ; ECX = 0 : the character number in the (sub)line.

        mov     [.i_chunk], ecx         ; set to 0

        mov     [.bytes], ecx           ; set to 0

        mov     [.last_space], ecx      ; set to 0
        mov     [.wrap_pos], ecx        ; set to 0
        mov     [.wrap_bytes], ecx      ; set to 0

        mov     [.start_out], ecx       ; set to 0
        mov     [.len_out], ecx         ; set to 0


.outer_loop:    ; it loops here on the start of every text chunk.

; load next syntax chunk parameters.

        push    ecx

        mov     ecx, [.i_chunk]
        cmp     ecx, [.chunk_cnt]
        jb      .get_chunk_params
        ja      .chunk_ok

; the remaining

        or      [.chunk_len], -1
        xor     eax, eax
        xor     edx, edx
        jmp     .get_chunk_colors

.get_chunk_params:

        movzx   eax, word [esi + TArray.array + sizeof.TTextChunk*ecx + TTextChunk.len]
        mov     [.chunk_len], eax

        movzx   eax, byte [esi + TArray.array + sizeof.TTextChunk*ecx + TTextChunk.color]
        mov     edx, eax
        shr     edx, 4
        and     eax, $0f

.get_chunk_colors:

        mov     eax, [ASM.clText + 4*eax]
        mov     edx, [ASM.clBackground + 4*edx]

        mov     [.chunk_color], eax
        mov     [.chunk_bkgnd], edx

.chunk_ok:

        inc     [.i_chunk]
        pop     ecx


.new_line:      ;<----------------------------------------------------------------------

; draw the line background

        get     eax, [.self], TWindow:Canvas
        stdcall DrawSolidRect, eax, [.startx], [.yStart], [.width_px], [.font_height], [.bg1]

        push    [.bg1] [.bg2]
        pop     [.bg1] [.bg2]

.inner_loop:            ;<----------------------------------------------------------------------

        cmp     ecx, [.skip]
        jne     .skip_ok

        mov     [.start_out], edi
        mov     eax, [.bytes]
        mov     [.len_out], eax

.skip_ok:
        cmp     ecx, [.max_char]
        jne     .end_ok

        call    .draw_processed
        and     [.start_out], 0

.end_ok:
        cmp     [.chunk_len], 0
        je      .end_of_the_chunk

        cmp     byte [edi], 0
        je      .end_of_line

        test    [ebx+TLineInfo.flags], aelfWordWrap
        jz      .word_wrap_ok

        cmp     ecx, [.wrap_width]
        jae     .do_word_wrap

.word_wrap_ok:

        stdcall DecodeUtf8, [edi]
        test    eax, eax
        jz      .end_of_line

        cmp     eax, $20
        jne     .space_ok

        mov     [.last_space], edi
        mov     [.wrap_pos], ecx
        push    [.bytes]
        pop     [.wrap_bytes]

        inc     [.last_space]
        inc     [.wrap_pos]
        inc     [.wrap_bytes]

.space_ok:

        add     edi, edx
        add     [.bytes], edx

        dec     [.chunk_len]
        inc     ecx

        jmp     .inner_loop


.do_word_wrap:

        mov     eax, [.last_space]
        mov     edx, [.bytes]

        test    eax, eax

        cmovnz  edi, eax                ; if there is no spaces on the line, wrap to where you are.
        cmovnz  ecx, [.wrap_pos]
        cmovnz  edx, [.wrap_bytes]

        mov     [.bytes], edx

        xor     eax, eax
        mov     [.last_space], eax
        mov     [.wrap_pos], eax
        mov     [.wrap_bytes], eax

        call    .draw_processed

        xor     ecx, ecx
        mov     [.start_out], ecx      ; start of a new subline.
        mov     [.len_out], ecx
        mov     [.bytes], ecx

        mov     eax, [.font_height]
        add     [.yStart], eax

        push    [.startx]
        pop     [.x]

        jmp     .new_line


.end_of_the_chunk:

        cmp     ecx, [.skip]
        jl      .outer_loop

        call    .draw_processed

        mov     [.start_out], edi       ; start of a new chunk.

        mov     eax, [.bytes]
        mov     [.len_out], eax
        jmp     .outer_loop


.end_of_line:

        call    .draw_processed


.finish:
        mov     edx, [.yStart]
        add     edx, [.font_height]

        mov     [esp+4*regEAX], edx
        popad
        return


.draw_processed:
        push    ebx ecx

        mov     eax, [.yStart]
        add     eax, [.font_height]
        js      .draw_ok

        mov     eax, [.bytes]
        sub     eax, [.len_out]
        jle     .draw_ok

        cmp     [.start_out], 0
        je      .draw_ok

        get     edx, [.self], TWindow:Canvas

        mov     ebx, [.x]
        cmp     ebx, [edx+TImage.width]
        jae     .draw_ok

        mov     ecx, [.yStart]
        add     ecx, [.font_asc]
        stdcall DrawString, edx, [.start_out], eax, ebx, ecx, [ASM.Font], [.chunk_color]
        add     [.x], eax

.draw_ok:
        pop     ecx ebx
        retn

endp


; Computes how many lines occupies some line of text,
; word wrapped on some width. Also computes the maximal
; width of the wrapped sub lines.
;
; Arguments:
;  .pLineInfo - pointer to TLineInfo structure with the text.
;  .WrapWidth - the width in characters to wrap the long lines.
;
; Returns:
;   EAX - the count of the extra lines if the line was wraped.
;   ECX - the maximal width of the line.

proc __ComputeWrapLines, .pLineInfo, .WrapWidth
.wrap_lines dd ?
.max_width  dd ?
.sub_width  dd ?
begin
        pushad

        mov     esi, [.pLineInfo]
        xor     ecx, ecx
        xor     ebx, ebx
        xor     edi, edi

        mov     [.wrap_lines], ecx
        mov     [.max_width], ecx
        mov     [.sub_width], ecx

.loop:

        cmp     ebx, [esi+TLineInfo.TextLen]
        jae     .end_of_line

        cmp     ecx, [.WrapWidth]
        jne     .word_wrap_ok

        test    edi, edi
        cmovnz  ebx, edi                ; if there is no spaces on the line, wrap to where you are.
        cmovnz  ecx, [.sub_width]

        cmp     ecx, [.max_width]
        cmovbe  ecx, [.max_width]
        mov     [.max_width], ecx

        inc     [.wrap_lines]           ; sublines count
        xor     ecx, ecx
        xor     edi, edi

.word_wrap_ok:

        mov     eax, ebx
        pos2ofs eax, esi

        movzx   eax, byte [esi+eax]

        cmp     eax, ' '
        jne     .next_char

        lea     edi, [ebx+1]
        mov     [.sub_width], ecx
        inc     [.sub_width]

.next_char:

        not     al              ; compute the length of the UTF-8 character by its first byte.
        bsr     edx, eax
        sub     edx, 7
        neg     edx
        jnz     @f
        inc     edx
@@:
        add     ebx, edx
        inc     ecx
        jmp     .loop


.end_of_line:

        cmp     ecx, [.max_width]
        cmovb   ecx, [.max_width]

        mov     eax, [.wrap_lines]

        mov     [esp+4*regECX], ecx
        mov     [esp+4*regEAX], eax

        popad
        return

endp









endmodule





include "themes/asmedit.asm"

include "syntax/SyntaxFASM.asm"