Tutorial 04: TText - Gap Buffer Basics

0
#
32
27.12.25 16:15

Tutorial 04: TText - Gap Buffer Basics

Overview

This tutorial introduces TText, FreshLib's gap buffer implementation for efficient text editing. Gap buffers are the data structure used by most text editors (including Emacs, Vi, and many others) because they provide O(1) insertion and deletion at the cursor position.

Topics Covered

  1. Gap Buffer Concept - Understanding the gap buffer data structure

  2. TText Structure - Memory layout and fields

  3. Coordinate Systems - Offset, Position, and Index

  4. Creating and Freeing - TextCreate, TextFree, TextDup

  5. Inserting Text - TextAddString, TextAddBytes, TextAddChar

  6. Deleting Text - TextDelChar, TextCompact

  7. Gap Management - TextMoveGap, TextSetGapSize

Prerequisites

  • Completion of Tutorial 01: StrLib Basics

  • Understanding of memory management

  • Familiarity with pointers and data structures

What is a Gap Buffer?

A gap buffer is a dynamic array with a "gap" (empty space) that moves to where edits occur. This makes insertion and deletion at the cursor position very fast.

Traditional Array (Slow)

Insert 'X' at position 5 in "Hello World":
Before: [H][e][l][l][o][ ][W][o][r][l][d]
After:  [H][e][l][l][o][X][ ][W][o][r][l][d]
         └── Must shift 6 characters ──┘

Gap Buffer (Fast)

Gap is at position 5:
Before: [H][e][l][l][o][_][_][_][_][W][o][r][l][d]
                        └── gap ──┘
After:  [H][e][l][l][o][X][_][_][_][W][o][r][l][d]
         Just write 'X' at gap position - O(1)!

When the cursor moves, the gap moves with it. Text on either side of the gap is shifted to maintain continuity.

TText Structure

TText uses a backward structure - the structure fields come BEFORE the data:

Memory Layout:
┌────────────┬───────────┬─────────┬─────────────┬──────────────────┐
│ .Length    │ .GapBegin │ .GapEnd │ .struc_size │ Text Data...     │
│ (dd)       │ (dd)      │ (dd)    │ (dd)        │                  │
└────────────┴───────────┴─────────┴─────────────┴──────────────────┘
             ↑                                     ↑
             Structure base (negative offsets)    Data starts here

Fields

Field Type Description
.Length dd Total buffer length in bytes (including gap)
.GapBegin dd Offset where gap starts
.GapEnd dd Offset where gap ends
.struc_size dd Size of structure header (sizeof.TText)

Important: The pointer returned by TextCreate points to the DATA, not the structure start. Access fields using negative offsets: [eax+TText.Length].

Three Coordinate Systems

TText uses three different ways to reference positions in text:

1. Offset (Bytes)

  • Raw byte position in the buffer

  • Includes the gap

  • Used internally by TText functions

  • Range: 0 to Length-1

2. Position (Bytes)

  • Logical byte position in the text (gap excluded)

  • What you see when reading the text

  • Used by most TText functions

  • Range: 0 to (Length - GapSize)

3. Index (Characters)

  • UTF-8 character position

  • Counts Unicode characters, not bytes

  • Used for cursor positioning

  • Range: 0 to character count

Example

Buffer: "Héllo" with gap at position 2
Offset:  [H][é][é][_][_][_][l][l][o]
         0  1  2  3  4  5  6  7  8
Position:0  1  2           3  4  5   (gap excluded)
Index:   0  1              2  3  4   (UTF-8 chars)

Note: 'é' is 2 bytes in UTF-8, so offset != position != index

Key Functions

Creation and Cleanup

; TextCreate
TextCreate .struc_size
  Returns: EAX = pointer to TText data (or 0 if error)
  Note: Pass sizeof.TText for .struc_size

; TextFree
TextFree .pText
  Returns: Nothing
  Note: Frees memory allocated by TextCreate

; TextDup
TextDup .pText
  Returns: EAX = pointer to duplicate TText
  Note: Creates a complete copy, must free separately

Insertion

; TextAddString - Insert StrLib string at position
TextAddString .pText, .position, .hString
  Position: -1 for end of text
  Returns: EAX = new gap end position

; TextAddBytes - Insert raw bytes
TextAddBytes .pText, .position, .pData, .DataLen
  Note: Doesn't need null-terminated data

; TextAddChar - Insert UTF-8 character
TextAddChar .pText, .char
  Note: Inserts at current gap position

Deletion

; TextDelChar - Delete character at gap position
TextDelChar .pText
  Returns: EAX = deleted character code
  Note: Expands gap by deletion

; TextCompact - Close gap and null-terminate
TextCompact .pText
  Returns: Continuous null-terminated string
  Note: Useful before displaying entire text

Gap Management

; TextMoveGap - Move gap to position
TextMoveGap .pText, .position
  Note: Essential before insertion/deletion

; TextSetGapSize - Ensure minimum gap size
TextSetGapSize .pText, .desired_size
  Note: Reallocates if needed

Demo Programs

Demo 13: Creating and Inspecting TText

File: demo13_ttext_create.asm

Demonstrates:

  • Creating TText with TextCreate

  • Inspecting structure with TextDebugInfo

  • Understanding gap position

  • Duplicating with TextDup

  • Cleaning up with TextFree

    Key Concepts:

  • Initial gap is at position 0

  • Default gap size (gapDefaultSize = 256 bytes)

  • Structure fields are negative offsets

Demo 14: Inserting Text

File: demo14_ttext_insert.asm

Demonstrates:

  • Inserting strings with TextAddString

  • Inserting raw bytes with TextAddBytes

  • Inserting characters with TextAddChar

  • Moving gap with TextMoveGap

  • Using position -1 for append

    Key Concepts:

  • Gap moves to insertion point

  • Subsequent insertions at same position are O(1)

  • Gap grows as needed

Demo 15: Deleting and Compacting

File: demo15_ttext_delete.asm

Demonstrates:

  • Deleting characters with TextDelChar

  • Compacting text with TextCompact

  • Setting gap size with TextSetGapSize

  • Reading resulting text

    Key Concepts:

  • Deletion expands the gap

  • Compact creates continuous string

  • After compact, gap is at end

Common Patterns

Creating and Using TText

; Create
stdcall TextCreate, sizeof.TText
test    eax, eax
jz      .error
mov     [pText], eax

; Add content
stdcall StrDupMem, <"Hello World">
stdcall TextAddString, [pText], -1, eax
stdcall StrDel, eax             ; Clean up string

; Use text...

; Cleanup
stdcall TextFree, [pText]

Inserting at Specific Position

; Move gap to position
stdcall TextMoveGap, [pText], 5

; Insert at gap
stdcall StrDupMem, <"INSERT">
stdcall TextAddString, [pText], 5, eax
stdcall StrDel, eax

Reading Complete Text

; Compact to get continuous string
stdcall TextCompact, [pText]

; Now pText points to null-terminated string
stdcall FileWriteString, [STDOUT], [pText]

Important Notes

Gap Position

  • The gap is always between characters, never inside a UTF-8 sequence

  • Position 0 = before first character

  • Position -1 = after last character

Memory Management

  • TextCreate allocates memory

  • TextDup creates a separate copy

  • TextFree must be called for each allocation

  • After TextCompact, don't call TextFree on the result (same pointer)

Position vs Offset

  • Never use offsets in application code

  • Always use positions (which exclude the gap)

  • Let TText functions handle offset conversion

UTF-8 Awareness

  • TextAddChar handles multi-byte UTF-8 correctly

  • TextDelChar deletes complete UTF-8 characters

  • Gap is positioned between character boundaries

Building and Running

cd 04-ttext-basics
./build.sh

This will compile and test all 3 demos.

Important Discoveries During Implementation

1. TextAddString Returns Updated Pointer in EDX

Problem: After calling TextAddString, the TText buffer may be reallocated to accommodate new text, invalidating the old pointer.

Discovery: TextAddString uses pushad/popad and returns the potentially reallocated pointer via mov [esp+4*regEDX], edx before popad. This means EDX contains the updated pointer after the call.

Solution: Always update your pText variable with EDX after each TextAddString call:

; WRONG - loses updated pointer after reallocation:
stdcall TextAddString, [pText], -1, cHello
; [pText] may now be invalid!

; CORRECT - capture updated pointer:
stdcall TextAddString, [pText], -1, cHello
mov     [pText], edx

Why This Matters: If the internal buffer needs to grow, TextSetGapSize (called by TextAddString) will use ResizeMem, which can move the entire buffer to a new memory location.


2. TextSetGapSize Also Returns Updated Pointer in EDX

Problem: TextSetGapSize can reallocate the buffer when enlarging the gap.

Discovery: Like TextAddString, it returns the updated pointer in EDX via the same mechanism.

Solution: Always update pText after calling TextSetGapSize:

stdcall TextSetGapSize, [pText], 512
jc      .error
mov     [pText], edx  ; CRITICAL - buffer may have moved

Why This Matters: Demo 15 crashed when trying to read TText fields after TextSetGapSize without updating the pointer. The old pointer was invalid, causing segmentation faults.


3. Inline Angle Bracket Strings Cause Crashes with TextAddString

Problem: Using FASM's inline string syntax <"text"> with TextAddString causes segmentation faults.

Discovery: Through debugging, found that StrPtr (called internally by TextAddString) was returning nonsensical values (like 0x14 = 20 decimal) when passed inline strings. The exact root cause in FASM's code generation is unclear, but inline strings generated by <> don't work correctly with TextAddString.

Solution: Always define text constants in the iglobal section using the text macro:

; WRONG - causes crash:
stdcall TextAddString, [pText], -1, <" ">
stdcall TextAddString, [pText], -1, <"Hello World">

; CORRECT - define constants:
iglobal
  cSpace      text " "
  cHelloWorld text "Hello World"
endg

stdcall TextAddString, [pText], -1, cSpace
stdcall TextAddString, [pText], -1, cHelloWorld

Debug Evidence:

hString = F5E45000    ; Valid StrLib handle
StrLen(hString) = 20  ; Wrong! "Hello" is 5 bytes
StrPtr(hString) = 14  ; Invalid pointer (0x14 = 20 decimal!)

This suggests the inline string mechanism interferes with StrLib's handle-to-pointer conversion.


4. TextDelChar Deletes AFTER Gap, Not BEFORE

Problem: Initial assumption was that TextDelChar deletes the character before the gap (like backspace). The demo showed "Hello World" unchanged after attempting to delete "World".

Discovery: Reading buffergap.asm:594, TextDelChar reads the byte at [edx+TText.GapEnd] and increments GapEnd, meaning it deletes the character AFTER the gap, expanding the gap forward.

Solution: Position the gap BEFORE the text you want to delete:

; To delete "World" from "Hello World":
; "Hello World" has 11 characters, position 6 = after "Hello "

; WRONG - gap after "World":
stdcall TextMoveGap, [pText], 11
stdcall TextDelChar, [pText]  ; Deletes nothing (at end)

; CORRECT - gap before "World":
stdcall TextMoveGap, [pText], 6
stdcall TextDelChar, [pText]  ; Deletes 'W'
stdcall TextDelChar, [pText]  ; Deletes 'o'
stdcall TextDelChar, [pText]  ; Deletes 'r'
stdcall TextDelChar, [pText]  ; Deletes 'l'
stdcall TextDelChar, [pText]  ; Deletes 'd'

Mental Model: Think of the gap as your cursor. TextDelChar acts like the DELETE key (deletes forward), not BACKSPACE (deletes backward).


5. TextCompact Should Only Be Called at End of Operations

Problem: Calling TextCompact in the middle of operations and then trying to continue using the TText caused crashes.

Discovery: TextCompact calls TextSetGapSize which can reallocate the buffer, but TextCompact itself doesn't return the updated pointer - it just does return without setting any return value.

Solution: Only call TextCompact when you're done with all modifications and just want to display or read the final result:

; WRONG - compact in middle of operations:
stdcall TextAddString, [pText], -1, cHello
mov     [pText], edx
stdcall TextCompact, [pText]  ; May reallocate
; [pText] might now be invalid!
stdcall TextMoveGap, [pText], 5  ; CRASH

; CORRECT - compact only at end:
stdcall TextAddString, [pText], -1, cHello
mov     [pText], edx
stdcall TextMoveGap, [pText], 5
stdcall TextDelChar, [pText]
; ... all modifications done ...
stdcall TextCompact, [pText]  ; Now safe to display
stdcall FileWriteString, [STDOUT], [pText]

Alternative: If you must inspect the text in the middle of operations, use TextDup to create a copy, compact the copy, and discard it.


6. Position -1 Works Correctly for Append Operations

Discovery: When testing TextAddString with position -1, it correctly appends to the end of the text.

How It Works: Looking at buffergap.asm:490 (TextMoveGap), when position is -1 (0xFFFFFFFF unsigned), the comparison cmp ecx, eax treats it as a huge number, and cmova ecx, eax clamps it to the maximum valid position.

; These are equivalent for appending:
stdcall TextAddString, [pText], -1, cWorld
; Same as:
stdcall TextAddString, [pText], [current_text_length], cWorld

This is a convenient shorthand that doesn't require calculating the current text length.


Summary of Best Practices

  1. Always update pText after functions that might reallocate:

    • TextAddStringmov [pText], edx

    • TextSetGapSizemov [pText], edx

  1. Define all text constants in iglobal:

    • Never use <"inline strings"> with TextAddString

    • Use text macro for all string constants

  1. Understand TextDelChar direction:

    • Deletes AFTER gap (forward direction)

    • Position gap BEFORE text to delete

  1. Call TextCompact only at the end:

    • Don't modify TText after compacting

    • Use for final display/output only

  1. Use position -1 for appending:

    • Convenient shorthand for "end of text"

    • No need to track current length


Next Steps

After completing this tutorial, continue to:

  • Tutorial 05: TText Advanced (searching, coordinates, Unicode)

Reference

31
27.12.25 16:16
; _______________________________________________________________________________________
;| FreshLib String Processing Tutorial: Demo 13 - TText Creation
;|_______________________________________________________________________________________|
;  Description: Demonstrates creating and inspecting TText gap buffer structures
;  Topics: TextCreate, TextDebugInfo, TextDup, TextFree
;  Prerequisites: Demo 01
;_________________________________________________________________________________________

include "%lib%/freshlib.inc"

LINUX_INTERPRETER equ './ld-musl-i386.so'

@BinaryType console, compact
LIB_MODE equ NOGUI

options.DebugMode = 0

include "%lib%/freshlib.asm"

; ========================================
; FUNCTION REFERENCE
; ========================================
;
; TextCreate
;   Description: Create a new TText gap buffer
;   Arguments:   .struc_size = sizeof.TText
;   Returns:     EAX = pointer to TText data (0 if error)
;   Note:        Creates buffer with default gap size (256 bytes)
;               Pointer points to DATA, not structure start
;
; TextFree
;   Description: Free TText structure
;   Arguments:   .pText = pointer to TText
;   Returns:     Nothing
;   Note:        Must be called for each TextCreate/TextDup
;
; TextDup
;   Description: Duplicate TText structure
;   Arguments:   .pText = source TText pointer
;   Returns:     EAX = pointer to duplicate TText
;   Note:        Creates independent copy, must free separately
;
; TextDebugInfo
;   Description: Display TText structure information
;   Arguments:   .pText = pointer to TText
;               .hLabel = string handle for label
;   Returns:     Nothing
;   Note:        Shows Length, GapBegin, GapEnd, text content
;
; ========================================

; ========================================
; Global Variables
; ========================================
uglobal
  pText1         dd ?
  pText2         dd ?
  hLabel         dd ?
endg

; ========================================
; Constants
; ========================================
iglobal
  cCRLF      text 13, 10

  cTitle     text "=== Demo 13: TText Creation ===", 13, 10, 13, 10

  cLabel1    text "1. Creating empty TText:", 13, 10
  cLabel2    text "2. TText structure information:", 13, 10
  cLabel3    text "3. Duplicating TText:", 13, 10

  cDone      text 13, 10, "Demo 13 complete!", 13, 10
  cError     text "Error creating TText!", 13, 10
endg

; ========================================
; Entry Point
; ========================================
start:
        InitializeAll

        stdcall FileWriteString, [STDOUT], cTitle

; ========================================
; Demo 1: Create empty TText
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel1

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText1], eax

        stdcall FileWriteString, [STDOUT], <"  TText created successfully", 13, 10>
        stdcall FileWriteString, [STDOUT], <"  Pointer: ">

; Display pointer value
        mov     eax, [pText1]
        stdcall NumToStr, eax, ntsHex or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

; Display structure fields
        mov     esi, [pText1]
        stdcall FileWriteString, [STDOUT], <"  Length: ">
        stdcall NumToStr, [esi+TText.Length], ntsDec or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

        stdcall FileWriteString, [STDOUT], <"  GapBegin: ">
        stdcall NumToStr, [esi+TText.GapBegin], ntsDec or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

        stdcall FileWriteString, [STDOUT], <"  GapEnd: ">
        stdcall NumToStr, [esi+TText.GapEnd], ntsDec or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

; ========================================
; Demo 2: TextDebugInfo
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel2

        stdcall StrDupMem, <"Empty TText">
        mov     [hLabel], eax

        stdcall TextDebugInfo, [pText1], [hLabel]
        stdcall StrDel, [hLabel]

; ========================================
; Demo 3: Duplicate TText
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel3

        stdcall TextDup, [pText1]
        test    eax, eax
        jz      .error
        mov     [pText2], eax

        stdcall FileWriteString, [STDOUT], <"  Duplicate created at: ">
        mov     eax, [pText2]
        stdcall NumToStr, eax, ntsHex or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

        stdcall FileWriteString, [STDOUT], <"  Original and duplicate are independent", 13, 10>

; Clean up
        stdcall TextFree, [pText1]
        stdcall TextFree, [pText2]

        stdcall FileWriteString, [STDOUT], cDone

.finish:
        FinalizeAll
        stdcall TerminateAll, 0

.error:
        stdcall FileWriteString, [STDOUT], cError
        jmp     .finish
30
27.12.25 16:16
; _______________________________________________________________________________________
;| FreshLib String Processing Tutorial: Demo 14 - TText Insertion
;|_______________________________________________________________________________________|
;  Description: Demonstrates inserting text into TText gap buffer
;  Topics: TextAddString, TextAddBytes, TextAddChar, TextMoveGap
;  Prerequisites: Demo 13
;_________________________________________________________________________________________

include "%lib%/freshlib.inc"

LINUX_INTERPRETER equ './ld-musl-i386.so'

@BinaryType console, compact
LIB_MODE equ NOGUI

options.DebugMode = 0

include "%lib%/freshlib.asm"

; ========================================
; FUNCTION REFERENCE
; ========================================
;
; TextAddString
;   Description: Insert StrLib string at position
;   Arguments:   .pText = TText pointer
;               .position = where to insert (0-based, -1 for end)
;               .hString = string handle
;   Returns:     EAX = bytes added
;   Note:        Gap moves to insertion point
;
; TextAddBytes
;   Description: Insert raw bytes at position
;   Arguments:   .pText = TText pointer
;               .position = where to insert
;               .pData = pointer to data
;               .DataLen = number of bytes
;   Returns:     EAX = bytes added
;   Note:        Doesn't require null termination
;
; TextAddChar
;   Description: Insert UTF-8 character at gap position
;   Arguments:   .pText = TText pointer
;               .char = UTF-8 character code
;   Returns:     EAX = bytes added
;   Note:        Inserts at current gap position only
;
; TextMoveGap
;   Description: Move gap to specified position
;   Arguments:   .pText = TText pointer
;               .position = target position
;   Returns:     Nothing
;   Note:        Call before TextAddChar to set insertion point
;
; TextCompact
;   Description: Close gap and create continuous string
;   Arguments:   .pText = TText pointer
;   Returns:     pText now points to null-terminated string
;   Note:        Gap moves to end
;
; See demo13 for: TextCreate, TextFree, TextDebugInfo
;
; ========================================

; ========================================
; Global Variables
; ========================================
uglobal
  pText          dd ?
  hString        dd ?
  hLabel         dd ?
endg

; ========================================
; Constants
; ========================================
iglobal
  cCRLF      text 13, 10

  cTitle     text "=== Demo 14: TText Insertion ===", 13, 10, 13, 10

  cLabel1    text "1. Insert string at position -1 (append):", 13, 10
  cLabel2    text "2. Insert string at position 0 (prepend):", 13, 10
  cLabel3    text "3. Insert string at position 6 (middle):", 13, 10
  cLabel4    text "4. Insert character at gap:", 13, 10

  cHello     text "Hello"
  cWorld     text "World"
  cCrazy     text "Crazy "
  cSpace     text " "
  cHelloSpace text "Hello "
  cHelloWorld text "Hello World"

  cDone      text 13, 10, "Demo 14 complete!", 13, 10
  cError     text "Error!", 13, 10
endg

; ========================================
; Entry Point
; ========================================
start:
        InitializeAll

        stdcall FileWriteString, [STDOUT], cTitle

; ========================================
; Demo 1: Append with position -1
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel1

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Append "Hello" - use raw text constant
        stdcall TextAddString, [pText], -1, cHello
        mov     [pText], edx

; Append " "
        stdcall TextAddString, [pText], -1, cSpace
        mov     [pText], edx

; Append "World"
        stdcall TextAddString, [pText], -1, cWorld
        mov     [pText], edx

; Display result
        stdcall TextCompact, [pText]
        stdcall FileWriteString, [STDOUT], <"  Result: '">
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10>

;        stdcall StrDupMem, <"After append">
;        mov     [hLabel], eax
;        stdcall TextDebugInfo, [pText], [hLabel]
;        stdcall StrDel, [hLabel]

        stdcall TextFree, [pText]

; ========================================
; Demo 2: Prepend with position 0
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel2

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Add "World" first
        stdcall TextAddString, [pText], -1, cWorld
        mov     [pText], edx

; Prepend "Hello "
        stdcall TextAddString, [pText], 0, cHelloSpace
        mov     [pText], edx

; Display result
        stdcall TextCompact, [pText]
        stdcall FileWriteString, [STDOUT], <"  Result: '">
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10, 13, 10>

        stdcall TextFree, [pText]

; ========================================
; Demo 3: Insert in middle
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel3

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Add "Hello World"
        stdcall TextAddString, [pText], -1, cHelloWorld
        mov     [pText], edx

; Insert "Crazy " at position 6
        stdcall TextAddString, [pText], 6, cCrazy
        mov     [pText], edx

; Display result
        stdcall TextCompact, [pText]
        stdcall FileWriteString, [STDOUT], <"  Result: '">
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10, 13, 10>

        stdcall TextFree, [pText]

; ========================================
; Demo 4: Insert character at gap
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel4

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Move gap to position 0
        stdcall TextMoveGap, [pText], 0

; Insert characters
        stdcall TextAddChar, [pText], 'H'
        stdcall TextAddChar, [pText], 'e'
        stdcall TextAddChar, [pText], 'l'
        stdcall TextAddChar, [pText], 'l'
        stdcall TextAddChar, [pText], 'o'

; Display result
        stdcall TextCompact, [pText]
        stdcall FileWriteString, [STDOUT], <"  Result: '">
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10>

        stdcall TextFree, [pText]

        stdcall FileWriteString, [STDOUT], cDone

.finish:
        FinalizeAll
        stdcall TerminateAll, 0

.error:
        stdcall FileWriteString, [STDOUT], cError
        jmp     .finish
29
27.12.25 16:16
; _______________________________________________________________________________________
;| FreshLib String Processing Tutorial: Demo 15 - TText Deletion
;|_______________________________________________________________________________________|
;  Description: Demonstrates deleting text and compacting TText gap buffer
;  Topics: TextDelChar, TextCompact, TextSetGapSize, TextMoveGap
;  Prerequisites: Demo 13, Demo 14
;_________________________________________________________________________________________

include "%lib%/freshlib.inc"

LINUX_INTERPRETER equ './ld-musl-i386.so'

@BinaryType console, compact
LIB_MODE equ NOGUI

options.DebugMode = 0

include "%lib%/freshlib.asm"

; ========================================
; FUNCTION REFERENCE
; ========================================
;
; TextDelChar
;   Description: Delete character at gap position
;   Arguments:   .pText = TText pointer
;   Returns:     EAX = deleted character code (0 if at end)
;   Note:        Expands gap by removing character before gap
;               Must call TextMoveGap first to position gap
;
; TextCompact
;   Description: Close gap and create continuous null-terminated string
;   Arguments:   .pText = TText pointer
;   Returns:     pText now points to continuous string
;   Note:        Gap moves to end, safe to read entire text
;
; TextSetGapSize
;   Description: Ensure minimum gap size
;   Arguments:   .pText = TText pointer
;               .desired_size = minimum gap size needed
;   Returns:     CF=0 on success, CF=1 on error
;   Note:        Reallocates buffer if needed
;
; See demo13 for: TextCreate, TextFree, TextDebugInfo
; See demo14 for: TextAddString, TextMoveGap
;
; ========================================

; ========================================
; Global Variables
; ========================================
uglobal
  pText          dd ?
  hString        dd ?
  hLabel         dd ?
  deletedChar    dd ?
endg

; ========================================
; Constants
; ========================================
iglobal
  cCRLF      text 13, 10

  cTitle     text "=== Demo 15: TText Deletion ===", 13, 10, 13, 10

  cLabel1    text "1. Delete characters from text:", 13, 10
  cLabel2    text "2. Gap expansion through deletion:", 13, 10
  cLabel3    text "3. Compacting text:", 13, 10
  cLabel4    text "4. Setting gap size:", 13, 10

  cHelloWorld text "Hello World"
  cABC        text "ABC"
  cHello      text "Hello"
  cWorld      text " World"
  cTo         text " to "

  cDone      text 13, 10, "Demo 15 complete!", 13, 10
  cError     text "Error!", 13, 10
endg

; ========================================
; Entry Point
; ========================================
start:
        InitializeAll

        stdcall FileWriteString, [STDOUT], cTitle

; ========================================
; Demo 1: Delete characters
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel1

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Add "Hello World"
        stdcall TextAddString, [pText], -1, cHelloWorld
        mov     [pText], edx

; Delete "World" by deleting at gap position
; (TextDelChar deletes character AFTER gap, expanding gap forward)
; So move gap to before what we want to delete
        stdcall TextMoveGap, [pText], 6   ; After "Hello ", before "World"

; Delete 5 characters: "World"
        stdcall TextDelChar, [pText]        ; W
        stdcall TextDelChar, [pText]        ; o
        stdcall TextDelChar, [pText]        ; r
        stdcall TextDelChar, [pText]        ; l
        stdcall TextDelChar, [pText]        ; d

        stdcall FileWriteString, [STDOUT], <"  After deleting 'World': '">
        stdcall TextCompact, [pText]
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10, 13, 10>

        stdcall TextFree, [pText]

; ========================================
; Demo 2: Gap expansion
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel2

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Add text
        stdcall TextAddString, [pText], -1, cABC
        mov     [pText], edx

; Show gap before deletion
        stdcall StrDupMem, <"Before deletion">
        mov     [hLabel], eax
        stdcall TextDebugInfo, [pText], [hLabel]
        stdcall StrDel, [hLabel]

; Move gap to position 2
        stdcall TextMoveGap, [pText], 2

; Delete one character
        stdcall TextDelChar, [pText]

; Show gap after deletion (gap expanded)
        stdcall StrDupMem, <"After deleting 1 char">
        mov     [hLabel], eax
        stdcall TextDebugInfo, [pText], [hLabel]
        stdcall StrDel, [hLabel]

        stdcall TextFree, [pText]

; ========================================
; Demo 3: Compacting
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel3

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Add text with gap in middle
        stdcall TextAddString, [pText], -1, cHello
        mov     [pText], edx

        stdcall TextMoveGap, [pText], 2    ; Gap in middle

        stdcall TextAddString, [pText], -1, cWorld
        mov     [pText], edx

; Before compact
        stdcall StrDupMem, <"Before compact">
        mov     [hLabel], eax
        stdcall TextDebugInfo, [pText], [hLabel]
        stdcall StrDel, [hLabel]

; Compact
        stdcall TextCompact, [pText]

; After compact
        stdcall StrDupMem, <"After compact">
        mov     [hLabel], eax
        stdcall TextDebugInfo, [pText], [hLabel]
        stdcall StrDel, [hLabel]

        stdcall FileWriteString, [STDOUT], <"  Compacted text: '">
        stdcall FileWriteString, [STDOUT], [pText]
        stdcall FileWriteString, [STDOUT], <"'", 13, 10, 13, 10>

        stdcall TextFree, [pText]

; ========================================
; Demo 4: Setting gap size
; ========================================
        stdcall FileWriteString, [STDOUT], cLabel4

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

; Set larger gap size
        stdcall TextSetGapSize, [pText], 512
        jc      .error
        mov     [pText], edx

        stdcall FileWriteString, [STDOUT], <"  Gap size set to 512 bytes", 13, 10>

; Show structure
        mov     esi, [pText]
        stdcall FileWriteString, [STDOUT], <"  Gap: ">
        stdcall NumToStr, [esi+TText.GapBegin], ntsDec or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax

        stdcall FileWriteString, [STDOUT], cTo
        stdcall NumToStr, [esi+TText.GapEnd], ntsDec or ntsUnsigned
        push    eax
        stdcall FileWriteString, [STDOUT], eax
        pop     eax
        stdcall StrDel, eax
        stdcall FileWriteString, [STDOUT], cCRLF

        stdcall TextFree, [pText]

        stdcall FileWriteString, [STDOUT], cDone

.finish:
        FinalizeAll
        stdcall TerminateAll, 0

.error:
        stdcall FileWriteString, [STDOUT], cError
        jmp     .finish
28
27.12.25 16:17
; Minimal TText test
include "%lib%/freshlib.inc"

LINUX_INTERPRETER equ './ld-musl-i386.so'

@BinaryType console, compact
LIB_MODE equ NOGUI

options.DebugMode = 0

include "%lib%/freshlib.asm"

uglobal
  pText dd ?
endg

iglobal
  cTest text "Hello"
  cCRLF text 13, 10
endg

start:
        InitializeAll

        stdcall FileWriteString, [STDOUT], <"Creating TText...", 13, 10>

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

        stdcall FileWriteString, [STDOUT], <"Adding string...", 13, 10>

        stdcall TextAddString, [pText], -1, cTest
        mov     [pText], edx

        stdcall FileWriteString, [STDOUT], <"Success!", 13, 10>

        stdcall TextFree, [pText]

        FinalizeAll
        stdcall TerminateAll, 0

.error:
        stdcall FileWriteString, [STDOUT], <"Error!", 13, 10>
        FinalizeAll
        stdcall TerminateAll, 1
27
27.12.25 16:17
; Minimal TText test
include "%lib%/freshlib.inc"

LINUX_INTERPRETER equ './ld-musl-i386.so'

@BinaryType console, compact
LIB_MODE equ NOGUI

options.DebugMode = 0

include "%lib%/freshlib.asm"

uglobal
  pText dd ?
endg

iglobal
  cTest text "Hello"
  cCRLF text 13, 10
endg

start:
        InitializeAll

        stdcall FileWriteString, [STDOUT], <"Creating TText...", 13, 10>

        stdcall TextCreate, sizeof.TText
        test    eax, eax
        jz      .error
        mov     [pText], eax

        stdcall FileWriteString, [STDOUT], <"Adding string...", 13, 10>

        stdcall TextAddString, [pText], -1, cTest
        mov     [pText], edx

        stdcall FileWriteString, [STDOUT], <"Success!", 13, 10>

        stdcall TextFree, [pText]

        FinalizeAll
        stdcall TerminateAll, 0

.error:
        stdcall FileWriteString, [STDOUT], <"Error!", 13, 10>
        FinalizeAll
        stdcall TerminateAll, 1

Tutorial 04: TText - Gap Buffer Basics

0
#