aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/edit_text.erl
diff options
context:
space:
mode:
authorlukeg <lukeg>2003-02-21 19:01:14 +0000
committerlukeg <lukeg>2003-02-21 19:01:14 +0000
commite7d48fe500f6ed676ee1b212ebd61408bced1c5b (patch)
tree11a756c7bb4906f3e186c1cb8331cb7ed27bc69c /src/edit_text.erl
downloadermacs-fork-e7d48fe500f6ed676ee1b212ebd61408bced1c5b.tar.gz
*** empty log message ***
Diffstat (limited to 'src/edit_text.erl')
-rw-r--r--src/edit_text.erl148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/edit_text.erl b/src/edit_text.erl
new file mode 100644
index 0000000..3bcc7d9
--- /dev/null
+++ b/src/edit_text.erl
@@ -0,0 +1,148 @@
+%%%----------------------------------------------------------------------
+%%% File : edit_text.erl
+%%% Author : Luke Gorrie <luke@bluetail.com>
+%%% Purpose : Text data structure with markers and undo
+%%% Created : 2 Oct 2001 by Luke Gorrie <luke@bluetail.com>
+%%%----------------------------------------------------------------------
+
+-module(edit_text).
+-author('luke@bluetail.com').
+
+-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 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 list(L) -> length(L);
+cbs_length(B) when 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.
+