-module(edit_history). -include("edit.hrl"). -compile({parse_transform, edit_transform}). -import(edit_lib, [buffer/1]). -define(HISTORY_MAX_LENGTH, 500). %% API -export([bindings/2, add/2]). %% Commands -export([move/4, search/4]). %% BaseVar = atom() name of edit_var variable to put history %% RegionFn = fun(Buffer) -> {Start, End} of history region %% %% Returns a set of (standard) keybindings. bindings(BaseVar, RegionFn) -> [{"M-p", {?MODULE, move, [BaseVar, RegionFn, up]}}, {"M-n", {?MODULE, move, [BaseVar, RegionFn, down]}}, {"M-r", {?MODULE, search, [BaseVar, RegionFn]}} ]. add(BaseVar, "") -> %% No blanks in history. skipped; add(BaseVar, Item) -> Hist = edit_var:lookup(BaseVar, []), if hd(Hist) == Item -> %% duplicate, leave history alone ok; true -> edit_var:set(BaseVar, trim_history([Item|Hist])) end. move(State, BaseVar, RegionFn, Direction) -> IdxVar = index_var(BaseVar), HVar = list_var(BaseVar), History = ["" | edit_var:lookup(HVar, [])], Idx = case continuing_p(State) of true -> Prev = edit_var:lookup(IdxVar, 1), case Direction of up -> Prev + 1; down -> Prev - 1 end; false -> 2 % 1 is a new empty string end, if Idx >= 1, length(History) >= Idx -> Buf = buffer(State), New = lists:nth(Idx, History), kill_old(Buf, RegionFn), edit_buf:insert(Buf, New, edit_buf:mark_pos(Buf, point)), edit_var:set(IdxVar, Idx), State; true -> edit_util:status_msg(State, "No more history") end. -command({search, [{string, "History regexp:"}]}). search(State, BaseVar, RegionFn, Regexp) -> History = edit_var:lookup(BaseVar, []), case find_match(History, Regexp) of {match, Cmd} -> Buf = buffer(State), kill_old(Buf, RegionFn), edit_buf:insert(Buf, Cmd, edit_buf:mark_pos(Buf, point)), State; {error, {ErrString, Position}} -> edit_util:status_msg(State, "Regex Error at " ++ integer_to_list(Position) ++ ": " ++ ErrString); nomatch -> edit_util:status_msg(State, "Not found") end. continuing_p(State) -> case State#state.lastcmd of {?MODULE, move, _} -> true; {?MODULE, search, _} -> true; {_, history_move, _} -> true; {_, history_search, _} -> true; _ -> false end. kill_old(Buf, RegionFn) -> {Start, End} = RegionFn(Buf), edit_buf:delete(Buf, Start, End). trim_history(Hist) when length(Hist) > ?HISTORY_MAX_LENGTH -> lists:sublist(Hist, ?HISTORY_MAX_LENGTH); trim_history(Hist) -> Hist. find_match(Strings, Regexp) -> case re:compile(Regexp) of {ok, MP} -> find_match1(Strings, MP); {error, Error} -> {error, Error} end. find_match1([], _Regexp) -> nomatch; find_match1([H|T], Regexp) -> case re:run(H, Regexp) of nomatch -> find_match(T, Regexp); match -> {match, H} end. list_var(Base) -> Base. index_var(Base) -> list_to_atom(atom_to_list(Base) ++ "_idx").