5. text editorsssw.jku.at/misc/ssw/05.texteditors.pdf5. text editors 5.1 data structures for texts...
TRANSCRIPT
1
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
2
Interface of a text type
interface Text {void loadFrom (InputStream s);void storeTo (OutputStream s);
char charAt (int pos);
void insert (int pos, char ch);void delete (int from, int to);
int indexOf (String pattern);}
These are the operations we are interested in
Alternative for insert() and delete()
...void replace (int from, int to, String s);...
replace(3, 3, "x"); ⇔ insert(3, x);replace(3, 5, ""); ⇔ delete(3, 5);replace(3, 5, "x");
3
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
4
SimpleText• Text is a character array• When text is inserted or deleted some other text has to be moved
class SimpleText {private char[] buf = new char[64];int len = 0;
void insert (int pos, char ch) {if (pos < 0) pos = 0;if (pos > len) pos = len;if (len == buf.length) {
char[] newBuf = new char[2 * buf.length];System.arraycopy(buf, 0, newBuf, 0, len);buf = newBuf;
}System.arraycopy(buf, pos, buf, pos+1, len-pos);buf[pos] = ch;len++;
}
...void delete (int from, int to) {
if (from < 0) from = 0;if (to > len) to = len;if (from >= to) return;System.arraycopy(buf, to, buf, from, len-to);len = len - (to - from);if (len < buf.length/2 && buf.length > 64) {
char[] newBuf = new char[buf.length/2];System.arraycopy(buf, 0, newBuf, 0, len);buf = newBuf;
}}...
}
Only practical for very short texts (< 256 characters)
insert(ch) delete(from, to)
5
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
6
GapTextObservation
The text buffer consists of a filled part and an empty part ("gap")
filled emptygap
Inserting and deleting at the gap position is efficient (nothing must be moved)
insert(ch)
delete(from, to)
Inserting and deleting in the middle of the text requires the remaining text to be moved
IdeaMove the gap to the insert/delete position!
7
Inserting characters
pos
Gap is moved to the insert position
pos
Now inserting at pos is efficient!
class GapText {char[] buf = new char[4096];int len = 0;int gapPos = 0;
void insert (int pos, char ch) {if (pos != gapPos) moveGapTo(pos);// pos == gapPosbuf[pos] = ch; len++; gapPos++;
}...
}
can be done withSystem.arraycopy(...)
gap size: buf.length - len
Typically, several characters are inserted/deleted at the same position, before this position is changed
8
Deleting text pieces
from toGap is moved to to
from toNow we just have to enlarge the gap (nothing must be moved).Typically, this happens if the delete key is pressed several times.
class GapText {...void delete (int from, int to) {
if (gapPos != to) moveGapTo(to);// gapPos == togapPos = from;len = len - (to - from);
}...
}
from to
gapPos +buf.length - len buf.lengthgapPos
9
GapText – pros and contrasAdvantages
• Efficient insert and delete• Simple to implement
Disadvantage
• The whole text must be in memory (may be a problem with very large texts)• Text length is restricted by the array size.
Array can be dynamically enlarged, but the text must be copied to the new array.
GapText is for example used in JFace of Eclipse
10
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
11
PieceListTextIdea
The text consists of pieces that are stored in a file
descriptors
text pieces
in memory
on file
The text pieces in a file need not be stored in consecutive order.They can even be stored in different files.
file 1
file 2
The descriptors specify the text pieces and their order
class Piece { // descriptorint len; // length of this pieceFile file; // file containing this pieceint filePos; // offset from beginning of filePiece next;
}
12
Inserting characters
p1
d1
pos
A text piece p2 is to be inserted at position pos
p1 is split at position pos
p11
d11
pos
d12
p12
p2 is inserted at position pos
p11
d11
pos
d12
p12
d2
p2
• Nothing must be moved on the file!• p2 can be stored in a different file
(typicaly, text that is typed in is storedin a scratch file)
• When the text is saved to diskits pieces are copied in the right order
p11 p2 p12
13
Implementation of the insert operation
class PieceListText {int len; // total text lengthPiece firstPiece; // first piece of the textFile scratch; // scratch file
void insert (int pos, int ch) {Piece p = split(pos); // split piece at posif (p is not last piece on scratch file) {
Piece q = new Piece(0, scratch, scratch.length());q.next = p.next; p.next = q;p = q;
}// p is last piece on scratch filescratch.write(ch);p.len++; len++;
}...
}
pos
There must always be a p, therefore there is an empty dummy piece atthe beginning of the text
scratch file
p
ch
Further insert(pos, ch) at the sameposition do not create a new scratchpiece, but append ch to the already existing scratch piece
scratch file
p qp
14
splitPiece split (int pos) {
if (pos == 0) return firstPiece;//--- set p to piece containing posPiece p = firstPiece;int len = p.len;while (pos > len) {
p = p.next;len = len + p.len;
}//--- split piece pif (pos != len) {
int len2 = len - pos;int len1 = p.len - len2;p.len = len1;Piece q = new Piece(len2, p.file, p.filePos + len1);q.next = p.next;p.next = q;
}return p;
}
5 10 3
pos = 9
firstPiecep
len = 15
5 4 3
pos = 9
firstPiecep q
6len1 len2
15
Deleting text
d1 d2 d3
from to
p1 p2 p3
The text between from and to is to be deleted
void delete (int from, int to) {Piece a = split(from);Piece b = split(to);a.next = b.next;
}
d11 d2 d31
from to
p11 p2 p31
Split
p12 p32
d12 d32a b
d11 d2 d31
from to
p11 p2 p31
Unlink descriptors between from and to
p12 p32
d12 d32
16
Larger exampleOpen a file with the text "abcde"
a b c d e
5
Insert "fgh" at position 3
a b c d e
3 23
f g h scratch file
3text = "abcfghde"
Delete text from 2 to 9
a b c d e
1 12
f g h
2 1
i j
2 9
2 1
text = "abe"a b c d e
2 1
Save the text and reopen it
a b e
3
Insert "ij" at position 5
a b c d e
3 22
f g h
2 1
i j5
text = "abcfgijhde"
scratch file
17
Fonts and stylesAre stored in the piece descriptors
class Piece {...Font font;Style style;
}
Times12plain
a b
setFont(a, b, "Arial", 10);
Times12plain
Arial10plain
Times12plain
c d
setStyle(c, d, italic);
Times12plain
Arial10plain
Times12italic
Arial10italic
Times12plain
18
Storing fonts and styles in a filePossible file format
File = textOffset {Piece} Text.Piece = length font style.
3 Times p 2 Arial i 2 Times p a b c d e f g
Font-Info ASCII-Text
After opening the file
a b c d e f g
Timesplain
Arialitalic
Timesplain
Pure text tools (compiler, grep, ...)can ignore the font information
19
Images and other objectsAre stored in a special piece of length 1
class ImagePiece extends Piece {Image img;
}
•
6 1 9
img
• An image is treated like a single character (the text contains a special character (e.g. ESC) at the position of the image)
• The image "floats" in the text while it is edited• When the text is saved the image is stored (serialized) together with the fonts and styles• In addition to images, any other objects can float in the text (buttons, page breaks, ...)
20
Pros and contras of PieceListTextAdvantages
• Only the descriptors are kept in memory; the text is always stored on file=> allows arbitrarily large texts, because they do not have to be kept in memory
• The text in the file is never modified.Nothing must be moved.
• Opening a text in the editor is fast, because the text does not have to be readinto memory
• Fonts and styles can be handled easily• Objects (images, ...) can float in the text
Disadvantage
• For displaying the text in a window it must be read from the file.But this is efficient due to buffering.Only the text which is displayed in the window has to be read.
21
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
22
Line descriptors
class Viewer extends Canvas {Text text;Line firstLine;int firstTpos;int lastTpos;...
}
abc defg h cr lf
ij klmn cr lf
opq rst uvwx yz cr lf
TOP
BOTTOM
LEFT
getHeight()
firstLine firstTpos
lastTpos
Text window
Line descriptor
class Line {String text;int len; int x, y, w, h;int base;Line prev, next;
}
i j k l m n(x, y)
h
w
basetext = "ij klmn\r\n"len = 9 (incl. \r, \n)
• Descriptors are only kept for visible lines• The text positions of lines are not stored so that they do not
have be updated during editing
getWidth()
(0,0)
23
Font metrics
p ascent
descent
width
height
lead
Graphics g = viewer.getGraphics();FontMetrics m = g.getFontMetrics();// m = new FontMetrics(font);
int ascent = m.getAscent();int descent = m.getDescent();int height = m.getHeight();int lead = m.getLead();int width = m.charWidth(ch);int swidth = m.stringWidth(string);
24
Filling the line descriptorsprivate Line fill (int top, int bottom, int pos) {
Line first = null, line = null;lastTpos = pos;char ch = text.charAt(pos);int y = top;while (y < bottom) {
if (first == null) { line = new Line(); first = line; }else {
Line prev = line;line.next = new Line(); line = line.next; line.prev = prev;
}StringBuffer buf = new StringBuffer();while (ch != '\n' && ch != EOF) {
buf.append(ch); pos++; ch = text.charAt(pos);}boolean eol = ch == '\n';if (eol) { buf.append(ch); pos++; ch = text.charAt(pos); }line.len = buf.length();line.text = buf.toString();line.x = LEFT;line.y = y;line.w = m.stringWidth(line.text);line.h = m.getHeight();line.base = y + m.getAscent();y += line.h;lastTpos += line.len;if (!eol) break;
}return first;
}
top
bottom
(x, y)
base h
w
...text
Viewer
pos
LEFT
25
Drawing the lines
class Viewer extends Canvas {...public void paint (Graphics g) {
if (firstLine == null) {firstLine = fill(TOP, getHeight() - BOTTOM, 0);caret = Pos(0);
}for (Line line = firstLine; line != null, line = line.next) {
g.drawString(line.text, line.x, line.base);}if (caret != null) invertCaret();if (sel != null) invertSelection(sel.beg, sel.end);
}}
paint() is automatically called by Java, if the window is to be redrawn
26
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
27
Positions
class Position {Line line; // line containing this positionint x, y; // base line point corresponding to this positionint tpos; // text position (offset relative to start of text)int org; // origin (text position of first character in this line)int off; // text offset from org
}
a b c d e f g h i j koff
(e.g. 7)
(x, y)
org(e.g. 100)
tpos(e.g. 107)
Information about the position of a character on the screen
28
Finding a positionprivate Position Pos (int x, int y) {
Position pos = new Position();if (y >= getHeight()-BOTTOM) y = getHeight()-BOTTOM-1;Line line = firstLine, last = null;pos.org = firstTpos;while (line != null && y >= line.y + line.h) {
pos.org += line.len; last = line; line = line.next;}if (line == null) { line = last; pos.org -= last.len; }pos.line = line;pos.y = line.base;if (x >= line.x + line.w) {
pos.x = line.x + line.w;pos.off = line.len;if (pos.org + line.len < text.length()) pos.off -= 2; // CR, LF
} else {pos.x = line.x;int i = pos.org;char ch = text.charAt(i);int w = m.charWidth(ch);while (x >= pos.x + w) {
pos.x += w;i++; ch = text.charAt(i);w = m.charWidth(ch);
}pos.off = i - pos.org;
}pos.tpos = pos.org + pos.off;return pos;
}
a b c d y
x
a b c dpos.off
(pos.x, pos.y)
pos.tpospos.org
pos.line
line.y
line.h
line.w
line.y
line.h
line.w
private Position Pos (int tpos) { ...}
29
CaretPosition caret = null;
private void setCaret (Position pos) {removeCaret(); removeSelection();caret = pos;invertCaret();
}
a b c d
public void setCaret (int x, int y) {setCaret (Pos(x, y));
}
public void setCaret (int tpos) {if (tpos >= firstTpos && tpos <= lastTpos) {
setCaret (Pos(tpos));} else removeCaret();
}
public void removeCaret () {if (caret != null) invertCaret();caret = null;
}
private void invertCaret () {g.setXORMode(Color.WHITE);int x = caret.x, y = caret.y;g.drawLine(x, y, x + 0, y); y++;g.drawLine(x, y, x + 1, y); y++;g.drawLine(x, y, x + 2, y); y++;g.drawLine(x, y, x + 3, y);g.setPaintMode();
}
(x,y)
30
Selectionclass Selection {
Position beg, end;Selection (Position a, Position b) {
beg = a; end = b;}
}
a b cj k l m no p q r s t u
d e f g h i
v w x
beg
end
private void invertSelection (Position beg, Position end) {g.setXORMode(Color.WHITE);Line line = beg.line;int x = beg.x;int y = line.y;int w;int h = line.h;while (line != end.line) {
w = LEFT + line.w - x;g.fillRect(x, y, w, h);line = line.next;x = line.x; y = line.y;
}w = end.x - x;g.fillRect(x, y, w, h);g.setPaintMode();
}
public void setSelection (int from, int to) {if (from < to) {
removeCaret(); removeSelection();Position beg = Pos(from);Position end = Pos(to);sel = new Selection(beg, end);invertSelection(beg, end);
} else removeSelection();}
public void removeSelection () {if (sel != null)
invertSelection(sel.beg, sel.end);sel = null;
}
31
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
32
Mouse inputRegister a listener for mouse events at the viewer
class Viewer extends Canvas {...public Viewer (...) {
...this.addMouseListener(new MouseAdapter() {
public void mousePressed (MouseEvent e) { doMousePressed(e); }public void mouseReleased (MouseEvent e) { doMouseReleased(e); }
});this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged (MouseEvent e) { doMouseDragged(e); }});
}...
}
mouse was pressed
mouse was released
mouse was movedwhile it was pressed
class MouseEvent extends InputEvent {int getX() {...}int getY() {...}...
}
(x, y) is relative to the viewer
33
Handling mouse eventsprivate void doMousePressed (MouseEvent e) {
removeCaret(); removeSelection();Position pos = Pos(e.getX(), e.getY());sel = new Selection(pos, pos);lastPos = pos;
}
private void doMouseDragged (MouseEvent e) {... // adjust sel.beg and sel.end
}
private void doMouseReleased (MouseEvent e) {if (sel.beg.tpos == sel.end.tpos) setCaret(sel.beg);lastPos = null;
}
34
Mouse draggingprivate void doMouseDragged (MouseEvent e) {
Position pos = Pos(e.getX(), e.getY());if (pos.tpos < sel.beg.tpos) {
if (lastPos.tpos == sel.end.tpos) {invertSelection(sel.beg, lastPos);sel.end = sel.beg;
}invertSelection(pos, sel.beg);sel.beg = pos;
} else if (pos.tpos > sel.end.tpos) {if (lastPos.tpos == sel.beg.tpos) {
invertSelection(lastPos, sel.end);sel.beg = sel.end;
}invertSelection(sel.end, pos);sel.end = pos;
} else if (pos.tpos < lastPos.tpos) { // beg <= pos <= end; clear pos..endinvertSelection(pos, sel.end);sel.end = pos;
} else if (lastPos.tpos < pos.tpos) { // beg <= pos <= end; clear beg..posinvertSelection(sel.beg, pos);sel.beg = pos;
}lastPos = pos;
}
a b c d e f gh i j k l m nbeg end
lastPos
pos
a b c d e f gh i j k l m nend
beg
a b c d e f gh i j k l m npos
endlastPos
beg
a b c d e f gh i j k l m nend
beg
a b c d e f g
lastPosendbeg pos
a b c d e f gbeg end
a b c d e f gend
lastPosbeg pos
a b c d e f gendbeg
35
Keyboard input
class Viewer extends Canvas {...public Viewer (...) {
...this.addKeyListener(new KeyAdapter() {
public void keyTyped (KeyEvent e) { doKeyTyped(e); }});...
}...
}
class KeyEvent extends InputEvent {char getKeyChar() {...}...
}
Register a listener for keyboard events at the viewer
36
Reacting to keyboard events
private void doKeyTyped (KeyEvent e) {boolean selection = sel != null;if (selection) {
text.delete(sel.beg.tpos, sel.end.tpos);// selection is removed// caret is set at sel.beg.pos
}if (caret != null) {
char ch = e.getKeyChar();
if (ch == BACKSPACE) {if (caret.tpos > 0 && !selection) {
int d = caret.off == 0 ? 2 : 1;text.delete(caret.tpos - d, caret.tpos);
}
} else if (ch == RETURN) {text.insert(caret.tpos, "\r\n");
} else { // normal character typedtext.insert(caret.tpos, String.valueOf(ch));
}}
}
a b c d e f gendbeg
a e f g
• Modifications are done in the text• The text notifies all its views
about the modification (MVC pattern)
37
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
38
MVC principle (Model View Controller)
void doKeyTyped (KeyEvent e) {...text.insert(pos, string);...
}
void insert (int pos, String s) {...fireUpdateEvent(new UpdateEvent(pos, pos, s));
}
void update (UpdateEvent e) {... // update screen area
}
void fireUpdateEvent (UpdateEvent e) {for (all listeners x) x.update(e);
}
Viewer
Text
...
Model
Controller + View
39
Implementation of the MVC principleclass UpdateEvent { // [from..to[ was replaced by text
int from, to;String text;UpdateEvent(int a, int b, String t) { from = a; to = b; text = t; }
}interface UpdateEventListener {
void update (UpdateEvent e);}
ArrayList listeners = new ArrayList();public void addUpdateEventListener (UpdateEventListener listener) {
listeners.add(listener);}public void removeUpdateEventListener (UpdateEventListener listener) {
listeners.remove(listener);}private void fireUpdateEvent (UpdateEvent e) {
Iterator iter = listeners.iterator();while (iter.hasNext()) {
UpdateEventListener listener = (UpdateEventListener)iter.next();listener.update(e);
}}
in class Text
public Viewer (...) {text.addUpdateEventListener(this);...
}
in class Viewer
40
Updating the viewer (insert)class Viewer extends Canvas implements UpdateEventListener {
...public void update (UpdateEvent e) {
if (e.from == e.to) { // insertPosition pos = Pos(e.from);int newCaretPos = pos.tpos + e.text.length();if (e.text.indexOf("\r\n") >= 0) { // insert across lines
rebuildFrom(pos);} else { // insert within a line
StringBuffer b = new StringBuffer(pos.line.text);b.insert(pos.off, e.text);pos.line.text = b.toString();pos.line.w += m.stringWidth(e.text);pos.line.len += e.text.length();lastTpos += e.text.length();repaint(pos.line.x, pos.line.y, getWidth(), pos.line.h);
}setCaret(newCaretPos);
} else if (e.text == null) { // delete...
}}...
}
rebuilds line descriptors starting atthe position pos and calls repaint()for the modified text area
41
Updating the viewer (delete)class Viewer extends Canvas implements UpdateListener {
...public void update (UpdateEvent e) {
if (e.from == e.to) { // insert...
} else if (e.text == null) { // deletePosition pos = Pos(e.to);int d = e.to - e.from;if (pos.off - d < 0) { // delete across lines
rebuildFrom(Pos(e.from));} else { // delete within a line
StringBuffer b = new StringBuffer(pos.line.text);b.delete(pos.off - d, pos.off);pos.line.text = b.toString();pos.line.w = m.stringWidth(pos.line.text);pos.line.len -= d;lastTpos -= d;repaint(pos.line.x, pos.line.y, getWidth(), pos.line.h);
}setCaret(e.from);
}}...
}
e.from e.to
posd
42
5. Text Editors5.1 Data structures for texts
5.1.1 SimpleText5.1.2 GapText5.1.3 PieceListText
5.2 Text representation on the screen (case study)5.2.1 Line descriptors5.2.2 Positions5.2.3 Input handling5.2.4 Updating the view5.2.5 Scrolling
43
ScrollBar and Viewer
JScrollBar scrollBar = new JScrollBar(Adjustable.VERTICAL, 0, 0, 0, 0);Viewer viewer = new Viewer(new Text(), scrollBar);
JPanel panel = new JPanel(new BorderLayout());panel.add("Center", viewer);panel.add("East", scrollBar);
scrollBar.addAdjustmentListener(viewer);
viewer.adjustmentValueChanged(e);
interface AdjustmentListener {void adjustmentValueChanged (AdjustmentEvent e);
}
class AdjustmentEvent {int getValue (); // new scrollbar value...
}
class JScrollBar {void setMinimum (int min);void setMaximum (int max);void setValue (int val);void setValues (int val, int extent,
int min, int max);...
}
Participating types
Interaction
value extent min max
44
Reaction to scroll eventsclass Viewer extends Canvas implements AdjustmentListener, ... {
...void adjustmentValueChanged (AdjustmentEvent e) {
int pos = e.getValue();if (pos > 0) { // find start of line
char ch;do { pos--; ch = text.charAt(pos); } while (pos > 0 && ch != '\n');if (pos > 0) pos++;
}if (pos != firstTpos) { // scroll
Position caret0 = caret;Selection sel0 = sel;removeSelection();removeCaret();firstTpos = pos;firstLine = fill(TOP, getHeight() - BOTTOM, firstTpos);repaint();if (caret0 != null) setCaret(caret0.tpos);if (sel0 != null) setSelection(sel0.beg.tpos, sel0.end.tpos);
}}...
}