Start page: ARABART home
← All fifteen programs

Text Editor: Sample GUI

1993 · C:\EDIT\EDIT.EXE

C:\EDIT\EDIT.EXE
Text Editor: Sample GUI running in DOS

Loading EDIT.EXE, 62 KB

Click the screen to type into the program.

The text editor he wrote to get his first job, with menus and windows, built on the Digi-Test interface.

The story

For his first professional job, Yasser was set an assignment: a text editor that loads and saves files, scrolls, moves the cursor and switches between insert and overwrite. He wrote two. For EDIT.EXE he took the window system of Digi-Test, his graduation project, and added one new C++ class, TextEditor. The menus, dialogs and windows came along for free. The old code hands every key to the new class through one virtual method call, without ever knowing the editor exists.

Built from his own parts

For a job interview he needed a text editor, fast. So he opened his graduation project, Digi-Test, kept its windows, menus and dialogs, and added one new thing: a text editor class.

It is what a developer does today when a new app starts from the component library of the last one. In EDIT.EXE you can count it: 89 of its 124 functions are the same as functions in Digi-Test, instruction for instruction. Step through to watch each part arrive, or pick any block to read it.

DIGI-TEST MAIN.EXE
EDIT.EXE 124 functions
02D122
04735
04BB8
054143
09171
092A10
0B233
0B2D32

35 are new. 22 of them are his TextEditor class. So 89 of 124 functions, and 12,674 of 17,393 instructions, came straight from Digi-Test.

The copy came whole, the way you would vendor a package into a project. Digi-Test’s circuit menus still sit inside EDIT.EXE, and so do the names of the five KEMYAM students who built it, his own last. Of all his text in the program, only three lines are new: the title EDIT-TEST Ver 1.00, the prompt for a file name, and the first file name, NONAME.TXT.

For engineers: how the functions were matched

Both programs were cut into functions at their entry code, and every function was turned into its list of instruction types, ignoring the addresses that change from one build to the next. That is opcode matching. Two lists that are equal mean the same source code. 124 of EDIT’s functions are long enough to compare (12 instructions or more); 89 match a Digi-Test function exactly, 12,674 of 17,393 instructions. Segment 02D1, his TextEditor class, matches nothing: it is all new.

EDIT.EXE, his text in the data segment
0264 "NODE CTRL N " ; Digi-Test: place a circuit node
0288 "VCC CTRL V " ; Digi-Test: place a power pin
02B4 " DETECT FAULT " ; Digi-Test: test a real circuit
02EB " TRUTH TABLE "
03BA " Yasser Mohamed " ; the last of the five KEMYAM names
009C "NONAME.TXT" ; new for EDIT
0313 " Name To Save/Load " ; new for EDIT
0401 "About This Program << EDIT-TEST Ver 1.00 >>" ; new for EDIT
Offsets are inside the data segment.

One new class

The new part is a C++ class, TextEditor: 22 functions in one block of memory, 6,688 bytes. One object of it takes 80 bytes. The first 42 are exactly a Digi-Test window: where it sits, how big it is, and which table of methods it uses. The other 38 are his: the cursor’s line and column, the tab width, insert or overwrite, room for 500 lines, and the file name. In today’s words, class TextEditor extends Window, written in Borland C++ in 1993. Step through the code that runs when the editor is made with new:

new TextEditor(...), his constructor
An 80-byte TextEditor object. Its first 42 bytes are a Digi-Test window whose field at 28h first points to Digi-Test’s method table 05EEh, then to his table 00ADh. Its other 38 bytes hold his fields and the name NONAME.TXT. Its field at 24h points to a 2,000-byte table of 500 line slots; slot 0 points to a 255-byte line. TextEditor object, 80 bytes Digi-Test window, 42 bytes00h to 07h: where it sits, its size 24h lines → 28h table 05EEh → 00ADh His fields, 38 bytes 36h line 0, 38h column 0 3Ah tab = 8 spaces 42h insert mode 4Ah room for 500 lines 4Ch name = "NONAME.TXT" Digi-Test’s table 05EEh 0 0B2D:07F2 draw1 0473:047F2 0473:046F key3 02D1:1A024 02D1:1A12 His table 00ADh 0 02D1:03B9 draw1 02D1:0C3E2 02D1:040D key3 02D1:1A02 same4 02D1:1A12 same Line table, 2,000 bytes 0000:0000 [0][1][2]…[499] 0000:00000000:00000000:0000 line 0 ↓ Line 0, 255 bytes 00 00 00 … 00
The 80-byte TextEditor object: a 42-byte Digi-Test window part with its method table at 28h, then his 38 bytes of fields, a 500-slot line table, line 0 and the file name. TextEditor object, 80 bytes Digi-Test window, 42 bytes00h to 07h: where it sits, its size 24h lines ↓ 28h table 05EEh → 00ADh His fields, 38 bytes 36h line 0, 38h column 0 3Ah tab = 8 spaces 42h insert mode, 4Ah 500 lines 4Ch name = "NONAME.TXT" His table 00ADh 0 02D1:03B9 draw1 02D1:0C3E2 02D1:040D key3 02D1:1A02 same4 02D1:1A12 same Line table, 2,000 bytes 0000:0000 [0][1]…[499] 0000:00000000:0000 line 0 ↓ Line 0, 255 bytes 00 00 00 … 00
  1. new asks the heap for 80 bytes: one empty TextEditor object.
  2. First, Digi-Test’s own window constructor runs, unchanged (0B2D:0004). It fills in the window part and writes its method table, 05EEh, at 28h.
  3. Then his constructor writes his own table, 00ADh, over it. From now on the object answers as an editor. Slots 3 and 4 point to the same code as before: inherited.
  4. His own fields: line 0, column 0, a tab of 8 spaces, insert mode, room for 500 lines.
  5. A second block, 2,000 bytes: the line table, 500 slots of 4 bytes each. Its address goes in the field at 24h.
  6. A loop clears all 500 slots to zero: no lines yet.
  7. Line 0 gets its own 255-byte block, all zeros, and the file name starts as NONAME.TXT. The editor is ready.
The same ideas today
1991 to 19932026Why it is the same idea
A TextEditor class built on Digi-Test’s window class class TextEditor extends Window in TypeScript The child gets everything the parent has and replaces only what it needs.
One hidden field: the address of the class’s method table The prototype link behind every JavaScript object One hidden pointer per object says where its methods live.

How a key finds the editor

Digi-Test’s key loop was written before the editor existed. It still hands every key to his new code, with three instructions and a table.

  1. Key loop0473:0257key = 1E61h, an a
  2. The applicationone objectfield 2Ah → the window in front
  3. His TextEditor object80 bytesfield 28h → table 00ADh
  4. Method table 00ADh
    1. 002D1:03B9draw
    2. 102D1:0C3E
    3. 202D1:040Dkey
    4. 302D1:1A02
    5. 402D1:1A12
  5. Runs02D1:040Dhis key handler
EDIT.EXE the key loop
  1. 0257call far 0B23:000C; read a key
  2. 025Fmov es:[bx],ax; keep it in the event
  3. 0269cmp ax,2D00h; Alt+X?
  4. 026Cje 0270; then stop
  5. 0294les bx,[bp+6]; the application
  6. 0297les bx,es:[bx+2Ah]; its window in front
  7. 029Bmov bx,es:[bx+28h]; that window’s method table
  8. 029Fcall far [bx+8]; slot 2: handle the key
  9. 02DBjmp 0257; next key
The same in C++
  1. for (;;) {
  2. ev.key = readKey();
  3. if (ev.key == ALT_X) break;
  4. Window far *w = app->front;
  5. w->handleKey(&ev);// virtual
  6. }
Watches
AX
1E61h the key: scan code 1Eh, letter a
BX
00ADh the method table
Help

Back to the top for the next key.

This is the heart of object-oriented code, and it is why his reuse worked. The loop only knows “the window in front” and asks it to handle the key. The window’s method table decides which code runs. Put a plain Digi-Test window in front, above, and the same three instructions land in Digi-Test’s handler instead. A browser does the same today when it hands a click to whatever element you clicked. So does a UI framework when it calls your component’s handler. The event loop stays the same, and each kind of window brings its own behavior.

Type into his line

Every line of text has its own block of 255 bytes. Type below, and his insert routine runs on every key.

EDIT-TEST NONAME.TXT
His insert, step by step
  1. if (ch == '\t') { n = tab; ch = ' '; }a Tab is 8 spaces
  2. if (col + n - 1 >= 255) return;is there room in the line?
  3. if (col > len) memset(line + len, ' ', col - len);past the end? fill the gap with spaces
  4. if (!overwrite || n > 1) movmem(line + col, line + col + n, 255 - col);insert: slide the rest right
  5. memset(line + col, ch, n);write the key

Click the line and type. An l fixes “Helo”: his code slides the rest of the line one place right, then writes the letter into the gap.

The editor keeps a table of 500 line slots, 2,000 bytes in all. A line gets its block only when you first type on it, so an empty file costs almost nothing. Insert is the in-place version of s.slice(0, i) + key + s.slice(i) in JavaScript. His movmem slides the rest of the line to the right inside its own block, and the key drops into the gap. No other line is ever touched.

Press Past the end, then type. EDIT lets the cursor stand anywhere on the line and fills the gap with spaces when a key arrives, a feature editors still call virtual space. Switch to Overwrite and a key replaces the byte under the cursor, while a Tab still opens 8 fresh spaces.

For engineers: the insert routine
EDIT.EXE 02D1:0D16, TextEditor insert
0D16 cmp byte ptr [bp+0Ah],9 ; is the key a Tab?
0D1F mov si,es:[bx+3Ah] ; yes: width = the tab width, 8
0D23 mov byte ptr [bp+0Ah],20h ; and write spaces
0D29 mov si,1 ; a letter: width 1
0D2F mov ax,es:[bx+38h] ; the cursor column
0D36 cmp ax,0FFh ; column + width - 1 inside the 255 bytes?
0D68 cmp es:[bx+38h],di ; cursor past the end of the text?
0D83 call far 0000:1D22 ; memset(line + len, 20h, col - len): fill the gap
0D8E cmp word ptr es:[bx+42h],0 ; insert mode?
0DC3 call far 0000:1D41 ; movmem(line + col, line + col + n, 255 - col)
0DE0 call far 0000:1D22 ; memset(line + col, ch, n): the key
1D22h and 1D41h are memset and movmem in Borland’s library. The arguments go on the stack before each call; the notes show them in C.

Every Byte of EDIT.EXE

Each square is one byte of the program in memory, colored by what it is. Tap a part to open it in the full map, down to the single instruction.

C:\EDIT\EDIT.EXE