Design of Entities and Operations within a Programmers' Editor
Table of Contents
I use editors as target examples in Software Engineering courses I teach. The materials below are hence deliberately incomplete. The context of this article is Programmers' Editor
1 Meta Commentary
- There is always some overlap between specs and design.
- The driving force in
- Specs is "what"
- Design is "how"
- These notes implicitly reference items explained in Editors
- Please report errors. I am afraid it is not error-free. Ignore spaces within the = = token.
2 Design of a Buffer
- An edit buffer can be designed as a (singly-, doubly-, circular- …) lists, or as a real array. This is straight forward, and too simplistic if we wish large files, in the 100+ MB range.
- We will use a non-obious design here just because ;-)
- Inspired by Emacs!
- The buffer B has several data members.
- left: seq of bytes;
- gapz: int;
- right: seq of bytes;
- dot: cursor initialized to (:=) (0, 0);
- mark: cursor := (0, 0);
- A few more will be introduced later.
- We will refer the combo of left + gap + right as simply the B.buf.
- B.specs.content = = B.design.left + B.design.right; That is, we are splitting the content into left and right parts. Why? And where is the split? Documented later.
- Before and after all public methods this conditions should hold. This is a class invariant.
- We are using the typical OOP notation of dot to relate the spec of an entity with its design.
- Below we drop ".design" because that is what is focussed in this document.
- We require that b.gapz >= 0. This gap is an area of "unused" memory of this size where new insertions happen.
- Design Decision: We will not track which windows are on a given buffer.
- Design Clarification: The '\n' is stored. When saving a buffer, it will become '\r\n' in Windows, '\r' in Mac OS, and remain as '\n' in Linux.
2.1 Public Methods: create
- Create a new buffer from an existing file named fnm. This is a constructor.
- Lower level design details: B.lz = = size of B.left in bytes; B.rz = = size of B.right in bytes; B.gz = = same as gapz; B.sz = = sum of these three.
- Design Decision: Where should the dot marks be? Dot at the beginning. Mark at the end.
- The design below is showing an expected OS API usage. This is too low level
just to show that sometimes we have to do that.
f := sys-call-open(fnm, for-reading); // assume exists b.sz = 1000; // adjust later b.buf := create a grow/shrink array initially of size b.sz bytes; b.fnm := fnm; b.dot := b.mark := (0, 0); b.left := b.right := empty; while not eof(f) { el := read-next-line(f); // stripped off the '\n' b.left += el; b.mark.y += 1; b.markx := #el; } sys-call-close(f);
- Note that the b.buf is expected to grow as needed in the += el line. We expect to use a library method native to the PL. This happens in spurts. b.right is initially empty. b.gapz = = the left over area.
2.2 Public Methods: destroy, insert, delete, cut, paste
3 Design of a Window
- The window W has a reference to a buffer B. Several more data members are introduced later.
3.1 Public Method: Create a Window W on B
- This is a constructor. Must the ww and hh be "given"?
- dot := B.dot;
- mark := B.dot;
- ww := 80; // why 80?
- hh := 25; // why 25?
- content: seq of ww x hh bytes;
- W.content = = W.spec.content; This is a class invariant.
- Design Clarification: We should replace bytes with UTF-n considerations.
- Design Clarification: W.content is computed from B.content, and the anchor point of the window given by W.leftx, W.topy coordinates that are relative to the buffer B. Refer to the idea of "padding" previosuly described.
- 0 <= W.topy < #B.lines
- 0 <= W.leftx < length of the longest line of B
- Call on the GUI of the system to show W.
3.2 Public Method: Move Window W Right by (dx, dy)
- For now assume dx > 0, dy > 0, both integers. So, intuitively, we are moving the window rightwards by dx and downwards by dy. The buffer scroll behind the window remains stationary.
- W.leftx += dx;
- W.topy += dy;
- We must maintain W.content = = W.spec.content class invariant.
- Design Decision: We must decide whether to adjust the current W.content or recompute it afresh from the new W.leftx, W.topy coordinates. This decision can be based on algorithmic simplicity (which has an effect on software maintenance) or speed of computation (users don't like lag). Exercise!
- Call on the GUI of the system to reflect the move of W.
3.3 Public Method: Destroy Window W
- B.dot := W.dot; // why?
- B.mark := W.mark; // why?
- Release W
3.4 Public Method: Hide Window W
- Design Decision: Is this the same as Destroy Window?
- Design Clarification: W is released. B.content remains unchanged.
3.5 Public Method: Show Window W
3.6 Public Method: Enlarge Window W
- Design Clarification: W.content changes, but B.content remains unchanged.
3.7 Public Method: Shrink Window W
- Design Clarification: W.content changes, but B.content remains unchanged.
4 Design of a Cursor
- Designed literally as specified. That is, a cursor is a pair (x, y), both non-negative integers.
- Cursors have no independent existence. They are always part of a window or a buffer.