Start page: ARABART home
← All fifteen programs

Text Editor: Demo

1993 · C:\EDIT\EDITOR.EXE

C:\EDIT\EDITOR.EXE
Text Editor: Demo running in DOS

Loading EDITOR.EXE, 62 KB

Click the screen to type into the program.

A second editor for the same job interview, with the look of Windows 3. He built it in three evenings.

The story

Yasser wrote a second editor for the same assignment from scratch, between Thursday 7 and Saturday 9 October 1993: three source files, 838 lines. WINGRAPH.CPP paints a Windows 3 look on the VGA screen with 5 palette colors of its own. EDITOR.CPP sends every key through a switch of 22 cases. FUNCTION.CPP keeps each line of text in its own 204-byte block, behind a table of 601 pointers. The red cursor is XOR drawing: draw it twice and it is gone.

Windows 3, drawn by hand

In 1993 everyone wanted the look of Windows 3. His editor has it, yet it is a DOS program: every line of that window is a call he wrote.

He drew it with Borland’s graphics library, BGI, on a 640 × 480 VGA screen with 16 colors. First he changes five of those colors, the way you would set design tokens: pure red, a true white, the two grays of Windows and a deeper blue. Then a few dozen calls draw the rest, each with coordinates he worked out by hand.

Step through his calls below, or tap one in the list: the pixels it adds flash yellow. This is his code running again, pixel for pixel, with his own font from the program folder. Switch to the standard colors to see what five palette changes do.

TEST-EDITOR 640×480
His TEST-EDITOR window: a blue title bar, a gray system menu box, a File menu with the F underlined, white paper, and a status bar reading INS ON.
0123456789101112131415
WINGRAPH.CPP

14 steps, from his five palette changes to the status bar: the finished window, exactly as his program draws it.

The small things are what make it Windows. There is the gray system menu box with its white bar, and the notches where the frame’s resize corners would be. There is even a one-pixel line under the F of File, because Alt+F really opens the menu. When the menu drops down, he first copies the screen under it with getimage and puts it back with putimage when it closes. That is a save-under, the trick window systems used until compositors arrived.

Then and now
1991 to 19932026Why it is the same idea
Five setrgbpalette calls, once, at start-up Theme colors as CSS custom properties Change a color in one place, and everything drawn with it follows.
getimage before the menu opens, putimage after A compositor that keeps every window in its own buffer Nothing behind a menu ever has to be drawn again.

A cursor that erases itself

His red cursor is not saved anywhere. It is three thin rectangles drawn in XOR mode, and drawing it a second time wipes it away.

TEST-EDITOR, zoomed
Three lines of his text in black on white, with the red cursor block on the second line, where the letter under it shows in cyan.
On the white paper
pixel111115
XOR00113
=110012
On a black letter
pixel00000
XOR00113
=00113

The cursor sits on the i of “This”. Move it, or press Draw it once more.

In XOR mode every new pixel is the old color XOR the pen color. He draws with color 3. On the white paper, color 15, 1111 XOR 0011 gives 1100: color 12, which he had just made pure red. On a black letter, 0000 XOR 0011 gives 0011, cyan, so the letter under the cursor still shows through.

Draw it again and every bit flips back. So to move the cursor he draws it once where it was and once where it goes. No copy of the screen, no redraw of the text, two calls. Today the cursor sits on its own layer, and the graphics card blends it in on every frame. In 1993, a little arithmetic did the same job.

22 keys, one table

His main loop waits for a key and hands it to a switch with 22 cases. Borland C++ turned that switch into a table inside the program, and a search of five instructions.

  1. 0135 8400hCtrl+PgUp to the top 0161: 00EA
  2. 0137 011BhEsc nothing at all 0163: 0077
  3. 0139 0E08hBackspace delete to the left 0165: 00D5
  4. 013B 0F09hTab six spaces 0167: 00FF
  5. 013D 1519hCtrl+Y delete the line 0169: 00E3
  6. 013F 1C0DhEnter a new line 016B: 00F8
  7. 0141 2100hAlt+F the File menu 016D: 0057
  8. 0143 2D00hAlt+X back to DOS 016F: 0052
  9. 0145 3C00hF2 Save, through the menu 0171: 0057
  10. 0147 3D00hF3 Open, through the menu 0173: 0057
  11. 0149 4400hF10 the File menu 0175: 0057
  12. 014B 4700hHome start of the line 0177: 00C7
  13. 014D 4800hUp up a line 0179: 00AB
  14. 014F 4900hPgUp up a page 017B: 00B9
  15. 0151 4B00hLeft left 017D: 009B
  16. 0153 4D00hRight right 017F: 00A3
  17. 0155 4F00hEnd end of the line 0181: 00CE
  18. 0157 5000hDown down a line 0183: 00B2
  19. 0159 5100hPgDn down a page 0185: 00C0
  20. 015B 5200hIns insert on or off 0187: 007F
  21. 015D 5300hDel delete 0189: 00DC
  22. 015F 7600hCtrl+PgDn to the last line 018B: 00F1
EDITOR.EXE main, 0262
  1. 001Ccall far bioskey; wait for a key
  2. 0022mov [bp-2],ax; keep it
  3. 0039mov di,[bp-2]; the key to look for
  4. 003Cmov cx,16h; 22 entries
  5. 003Fmov bx,0135h; the table, inside the code
  6. 0042cmp cs:[bx],di; this entry’s key?
  7. 0045je 004E; found it
  8. 0047inc bx; next entry,
  9. 0048inc bx; 2 bytes on
  10. 0049loop 0042; at most 22 tries
  11. 004Bjmp 0114; not in the table: type it
  12. 004Ejmp cs:[bx+2Ch]; its handler, 44 bytes on
EDITOR.CPP, as C
  1. int key = bioskey(0);
  2. switch (key) {
  3. case CTRL_PGUP: top(); break;
  4. case ESC: break;
  5. case BACKSPACE: back(); break;
  6. case TAB: spaces(6); break;
  7. case CTRL_Y: delLine(); break;
  8. case ENTER: newLine(); break;
  9. case ALT_X: quit();
  10. case ALT_F: case F2:
  11. case F3: case F10: menu(key); break;
  12. case HOME: home(); break;
  13. case UP: up(); break;
  14. case PGUP: pageUp(); break;
  15. case LEFT: left(); break;
  16. case RIGHT: right(); break;
  17. case END: end(); break;
  18. case DOWN: down(); break;
  19. case PGDN: pageDown(); break;
  20. case INS: insMode(); break;
  21. case DEL: del(); break;
  22. case CTRL_PGDN: bottom(); break;
  23. default: if ((char)key) ins(key);
  24. }
Watches
DI
4800h the key: Up
BX
014Dh entry 12
CX
10 tries left
Help

44 bytes past this entry sits its handler’s address, 00ABh. Jump there: up a line.

The table holds the 22 key codes, and 44 bytes further on, the 22 addresses of their handlers. The search walks the keys with loop; when one matches, the jump reads the address 44 bytes past it. It is a switch statement as data, the same idea as a JavaScript object of handlers: handlers[key](). A key that is not in the table but carries a letter goes into the line, and Tab types six spaces.

One pointer per line

The text lives in a table of 601 pointers. Each line is its own 204-byte block, made with new only when the text needs it.

TEST-EDITOR
His editor with two lines: Hello, World! and This is a text editor!, with the red cursor at the end of the second line.
LINE TABLE 601 pointers
  1. [0]1C2A:0004
  2. [1]1C38:0004
  3. [2]0000:0000
  4. …
  5. [600]0000:0000
The heap
  1. 1C2A:0004Hello, World!13 of 204
  2. 1C38:0004This is a text editor!22 of 204

Two lines, two blocks, two pointers. Put the cursor in the middle of a line and press Enter.

Press Enter in the middle of a line and watch the two panels. The editor asks the heap for a new block, moves the text after the cursor into it, and slides the pointers below it down one slot. Each pointer is just 4 bytes, a far pointer, so a whole new line costs a handful of bytes of moving. The other lines’ letters never move at all.

VS Code keeps your file the same way at a far bigger scale. Its piece tree never moves the text you loaded: edits become small pieces, and a balanced tree of pointers says in which order to read them. Both designs keep typing fast by moving references instead of text.

Then and now
1991 to 19932026Why it is the same idea
A table of 601 pointers, one per line VS Code’s piece tree, a tree of pointers into the text Move the small references, never the text they point to.
new char[204] for each line, when it is needed Allocating a node only when a line is created An empty document costs almost nothing.
For engineers: typing a letter, as compiled
EDITOR.EXE, FUNCTION.CPP, segment 027A
035D mov bx,[13E0h] / shl bx,2 ; the current line, times 4: its slot
036C call far strlen ; how long is the line?
0391 call 01CF ; XOR the cursor away first
039B mov ax,0CCh / sub ax,[13DEh] ; 204 minus the column
03CC call far memmove ; open a one-letter gap at the cursor
03F4 call far setmem ; past the end? fill with spaces
0403 les bx,[bx+0A72h] ; the line’s address, from the table
040E mov es:[bx],al ; store the letter
0411 cmp word ptr [13DAh],4Ch ; screen column 76? scroll 10 columns
Two instructions share a line where marked with a slash. His variables: 13E0h the current line, 13DEh the column in the line, 13DAh the column on screen, 015Fh the number of lines, 015Eh insert mode. The table at 0A72h is 601 far pointers, 2,404 bytes.

Three evenings

The job interview asked for an editor that loads, saves, scrolls, switches between insert and overwrite, and moves the cursor. His project file kept the time of every save.

  1. Thursday 7 October 1993
    1. first file saved
    2. a second file
  2. Friday 8 October
    1. a file saved
    2. WINGRAPH.CPP
    3. a file saved
    4. EDITOR.CPP
  3. Saturday 9 October
    1. a file saved
    2. FUNCTION.CPP
    3. EDITOR.EXE built

Sat 20:47:48

Six seconds later, the last build: EDITOR.EXE. 838 lines of C++ became 6,709 bytes of his code.

D:\EDITOR
NameLinesCodeLast saved
WINGRAPH.CPP1591,718 BFri 18:02
EDITOR.CPP119393 BFri 19:27
FUNCTION.CPP5604,598 BSat 20:47
EDITOR.EXE8386,709 BSat 20:47:48

EDITOR.PRJ also says where everything lived: the project in D:\EDITOR, Borland C++ 2 in F:\BC2, with the Turbo Assembler and Turbo Debugger one menu away. His include path even lists C:\SUN, the folder of The Brave Sun. And the desktop file, EDITOR.DSK, remembers the three windows open in the IDE at the end: EDITOR.CPP, WINGRAPH.CPP and a scratch file, X.CPP. Like an editor today that reopens your tabs. The code sizes in the project file match the program byte for byte. Its 1,718 + 393 + 4,598 bytes are exactly where each file’s code begins and ends in EDITOR.EXE.

Every Byte of EDITOR.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\EDITOR.EXE