1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
-module(edit_text).
-export([new/0, new/1, replace/4, add_mark/4, move_mark/3, mark_pos/2,
undo/2, cord/1, walk_backward/3, walk_forward/3]).
-record(text, {cord, marks=[], undo=[], running_undo=[]}).
-record(mark, {name, pos, direction}).
%% ----------------------------------------------------------------------
%% new
new() ->
new(<<>>).
new(BCS) when is_list(BCS) ->
new(cord:new(BCS));
new(Cord) ->
#text{cord=Cord}.
%% ----------------------------------------------------------------------
%% replace (CBS means Cord, Binary, or String)
replace(Text0 = #text{cord=Cord0,marks=Marks0,undo=Undo0}, CBS, Start, Len) ->
Cord1 = cord:replace(Cord0, CBS, Start, Len),
Marks1 = update_marks(Marks0, Start, Start+Len, cbs_length(CBS)),
Undo1 = [{Cord0, Marks0} | Undo0],
%% FIXME: maybe better to chain undo through states (i.e. just
%% know about previous state, which knows about previous state,
%% ...) - if that plays nicely with "running undo"
Text0#text{cord=Cord1, marks=Marks1, undo=Undo1, running_undo=[]}.
cbs_length(L) when is_list(L) -> length(L);
cbs_length(B) when is_binary(B) -> size(B);
cbs_length(C) -> cord:cord_size(C).
update_marks(Marks, Start, End, Len) ->
[update_mark(Mark,Start,End,Len) || Mark <- Marks].
%% Inside the replaced region (includes being on an inserted character)
update_mark(Mark, Start, End, Len)
when Mark#mark.pos >= Start,
Mark#mark.pos < End;
Mark#mark.pos == Start ->
case Mark#mark.direction of
forward ->
Mark#mark{pos=Start+Len};
backward ->
Mark#mark{pos=Start}
end;
%% After the replaced region
update_mark(Mark, Start, End, Len) when Mark#mark.pos >= End ->
%% We move forward by the length-delta
Mark#mark{pos = Mark#mark.pos - (End - Start) + Len};
%% Before the replaced region
update_mark(Mark, _Start, _End, _Len) ->
Mark.
%% ----------------------------------------------------------------------
%% add_mark
add_mark(Text = #text{marks=Marks0}, Name, Pos, Direction) ->
Mark = #mark{name=Name, pos=Pos, direction=Direction},
Text#text{marks=[Mark|Marks0]}.
%% ----------------------------------------------------------------------
%% move_mark
move_mark(Text0, Name, Pos) ->
{Front, [M | Back]} =
lists:splitwith(fun(X) -> X#mark.name /= Name end,
Text0#text.marks),
NewMarks = Front ++ [M#mark{pos=Pos} | Back],
Text0#text{marks=NewMarks}.
%% ----------------------------------------------------------------------
%% mark_pos
mark_pos(#text{marks=Marks}, Name) ->
{found, Mark} = find(fun(M) -> M#mark.name == Name end, Marks),
Mark#mark.pos.
find(_Pred, []) ->
not_found;
find(Pred, [H|T]) ->
case Pred(H) of
true ->
{found, H};
false ->
find(Pred, T)
end.
%% ----------------------------------------------------------------------
%% walk_backward
walk_backward(Text = #text{cord=Cord}, Fun, Pos) ->
Size = cord:cord_size(Cord),
if Pos == Size + 1 ->
case Fun(not_there) of
{more, FNext} ->
walk_backward(Text, FNext, Pos-1);
{result, R} ->
R
end;
true ->
cord:walk(Cord, Pos, backward, Fun)
end.
%% ----------------------------------------------------------------------
%% walk_forward
walk_forward(#text{cord=Cord}, Fun, Pos) ->
cord:walk(Cord, Pos, forward, Fun).
%% ----------------------------------------------------------------------
%% undo
undo(Text0 = #text{undo=Undo, running_undo=Running}, Continuing) ->
Text1 = case Continuing of
true ->
Text0;
false ->
Text0#text{running_undo=Undo}
end,
case Running of
%% Nothing to undo - recycle redo list
[] ->
Text1#text{running_undo=Undo};
[{UndoCord, UndoMarks}|Undos] ->
Text1#text{cord=UndoCord,
marks=UndoMarks,
undo=[{Text0#text.cord, Text0#text.marks} | Undo],
running_undo=Undos}
end.
%% ----------------------------------------------------------------------
%% cord
cord(#text{cord=Cord}) -> Cord.
|