aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/edit_util.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/edit_util.erl')
-rw-r--r--src/edit_util.erl245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/edit_util.erl b/src/edit_util.erl
new file mode 100644
index 0000000..c539277
--- /dev/null
+++ b/src/edit_util.erl
@@ -0,0 +1,245 @@
+%%%----------------------------------------------------------------------
+%%% File : edit_util.erl
+%%% Author : Luke Gorrie <luke@bluetail.com>
+%%% Purpose : Utility functions
+%%% Created : 15 Oct 2000 by Luke Gorrie <luke@bluetail.com>
+%%%----------------------------------------------------------------------
+
+-module(edit_util).
+-author('luke@bluetail.com').
+
+-include_lib("ermacs/include/edit.hrl").
+
+-export([keyname/1, status_msg/2, status_msg/3, update_minibuffer_window/2,
+ select_window/2, update/3]).
+
+-compile(export_all).
+
+keyname(263) -> "C-h";
+keyname(127) -> "C-h";
+keyname(272) -> "C-h";
+keyname(C) ->
+ case ctrl_p(C) of
+ true ->
+ "C-" ++ keyname(tolower(unctrl(C)));
+ false ->
+ case meta_p(C) of
+ true ->
+ "M-" ++ keyname(tolower(unmeta(C)));
+ false ->
+ [C]
+ end
+ end.
+
+%% Eighth bit set means meta
+meta_p(C) -> C band meta_bit() /= 0.
+unmeta(C) -> C band (bnot meta_bit()).
+
+meta_bit() -> 2#10000000.
+
+ctrl_p(C) -> Base = unmeta(C), ((Base band 2#01000000) == 0) and (Base < 32).
+unctrl(C) -> C + 64.
+
+tolower(C) when C >= $A, C =< $Z ->
+ C - $A + $a;
+tolower(C) ->
+ C.
+
+%% Show a one-line message in the status line (where the minibuffer is)
+status_msg(State, Fmt, Args) ->
+ status_msg(State, io_lib:format(Fmt, Args)).
+status_msg(State, String) ->
+ Text = lists:flatten(String),
+ AllWindows = [State#state.curwin | State#state.windows],
+ NewWindows = update(fun(Win) -> Win#window{status_text=Text} end,
+ fun(Win) -> Win#window.minibuffer end,
+ AllWindows),
+ State#state{curwin=hd(NewWindows),
+ windows=tl(NewWindows)}.
+
+kill_newlines([]) ->
+ [];
+kill_newlines([$\n|T]) ->
+ [$ |
+ kill_newlines(lists:dropwhile(fun(C) -> C == $ end, T))];
+kill_newlines([H|T]) ->
+ [H|kill_newlines(T)].
+
+%% Apply Fun to minibuffer window and put the result in the state.
+update_minibuffer_window(State, Fun) ->
+ AllWindows = [State#state.curwin | State#state.windows],
+ NewWindows = update(Fun,
+ fun(Win) -> Win#window.minibuffer end,
+ AllWindows),
+ State#state{curwin=hd(NewWindows),
+ windows=tl(NewWindows)}.
+
+%% Map over all windows in State with Fun.
+%% Fun is fun(Win) -> Win'
+window_map(State, Fun) ->
+ State#state{curwin=Fun(State#state.curwin),
+ windows=lists:map(Fun, State#state.windows)}.
+
+%% Return a list of all windows in State.
+windows(State) ->
+ [State#state.curwin | State#state.windows].
+
+%% Use F to update the first item of L which satisfies P.
+%% e.g.
+update(F, P, []) ->
+ [];
+update(F, P, [H|T]) ->
+ case P(H) of
+ true ->
+ [F(H)|T];
+ false ->
+ [H|update(F, P, T)]
+ end.
+
+%% Select the first window satisfying P, or crash if no match
+select_window(State, P) ->
+ case P(State#state.curwin) of
+ true ->
+ State;
+ false ->
+ select_window1(State, P, State#state.windows, [State#state.curwin])
+ end.
+
+select_window1(State, P, [], Acc) ->
+ exit(nomatch);
+select_window1(State, P, [H|T], Acc) ->
+ case P(H) of
+ true ->
+ State#state{curwin=H,
+ windows=T ++ lists:reverse(Acc)};
+ false ->
+ select_window1(State, P, T, [H|Acc])
+ end.
+
+set_buffer(State, Buffer) ->
+ Win = edit_window:attach(State#state.curwin, Buffer),
+ State#state{curwin = Win,
+ buffers = [Buffer | (State#state.buffers -- [Buffer])]}.
+
+%% Display the message `String'. If it's only one line, it's shown in
+%% the status line. Otherwise, a buffer called `Buf' is popped up to
+%% show it.
+popup_message(State, Buf, String) ->
+ popup_message(State, Buf, "~s", [String]).
+
+popup_message(State, Buf, Format, Args) ->
+ String = lists:flatten(io_lib:format(Format, Args)),
+ case monoline(String) of
+ {true, Line} ->
+ edit_util:status_msg(State, Line);
+ false ->
+ edit_buf:new(Buf), % might be already started, s'ok
+ edit_buf:set_text(Buf, String),
+ popup_buffer(State, Buf)
+ end.
+
+%% See if a string fits on one line.
+%% Returns: {true, Line} | false
+%% Where Line is the same as the input string, but with no newlines.
+monoline(S) -> monoline(S, []).
+
+monoline([$\n], Acc) -> {true, lists:reverse(Acc)};
+monoline([], Acc) -> {true, lists:reverse(Acc)};
+monoline([$\n|T], Acc) -> false;
+monoline([H|T], Acc) -> monoline(T, [H|Acc]).
+
+%% Make `Buffer' visible (but not selected) in a window - either by hijacking
+%% an existing window or creating a new one.
+popup_buffer(State, Buffer) ->
+ case lists:any(fun(W) -> W#window.buffer == Buffer end, windows(State)) of
+ true ->
+ %% already visible
+ State;
+ false ->
+ popup_buffer1(State, Buffer)
+ end.
+
+popup_buffer1(State = #state{windows=[MB]}, Buffer)
+ when MB#window.minibuffer == true ->
+ %% Clause: One normal window, which is curwin. Split it.
+ {A, B} = split_window(State#state.curwin),
+ popup_buffer1(State#state{curwin=A,
+ windows=[B,MB]},
+ Buffer);
+popup_buffer1(State = #state{windows=[Win]}, Buffer)
+ when (State#state.curwin)#window.minibuffer == true ->
+ %% Clause: One normal window, minibuffer is curwin. Split normal
+ %% window.
+ {A, B} = split_window(Win),
+ popup_buffer1(State#state{windows=[A,B]}, Buffer);
+popup_buffer1(State, Buffer) when State#state.windows > 1 ->
+ %% OK! The first non-minibuffer is used.
+ State#state{windows=popup_buffer_in(State#state.windows, Buffer)}.
+
+popup_buffer_in([W|Ws], Buffer) when W#window.minibuffer == false ->
+ [edit_window:attach(W, Buffer) | Ws];
+popup_buffer_in([MB|Ws], Buffer) ->
+ [MB | popup_buffer_in(Ws, Buffer)].
+
+split_window(W) ->
+ Height = W#window.height,
+ HeightA = trunc(Height / 2),
+ HeightB = Height - HeightA,
+ io:format("Height = ~p~nHeightA = ~p~nHeightB = ~p~n",
+ [Height, HeightA, HeightB]),
+ New = edit_window:make_window(W#window.buffer,
+ W#window.y + HeightA,
+ W#window.width,
+ HeightB),
+ {W#window{height=HeightA}, New}.
+
+%% spawn process with some borrowed buffers
+
+spawn_with(Buffer, M, F, A) ->
+ spawn_with(Buffer, {M, F, A}).
+
+spawn_with(Buffers, What) ->
+ Ref = make_ref(),
+ Pid = spawn_link(?MODULE, spawn_with_init, [self(), Ref, Buffers, What]),
+ receive {ready, Ref} -> ok end.
+
+spawn_with_init(Pid, Ref, Buffers, What) ->
+ lists:foreach(fun(Buf) -> edit_buf:borrow(Buf) end,
+ Buffers),
+ Pid ! {ready, Ref},
+ spawn_with_apply(What),
+ %% we miss this redraw if the command crashes. oops.
+ edit:invoke_later(?MODULE, redraw, []).
+
+spawn_with_apply({M, F, A}) ->
+ apply(M, F, A);
+spawn_with_apply(Fun) when function(Fun) ->
+ Fun().
+
+
+redraw(State) ->
+ %% Actually we needn't do anything, because the screen redraws
+ %% after each command. (What a pointless abstraction :-)
+ State.
+
+%% Get the current working directory for the state or buffer.
+pwd(State) when record(State, state) ->
+ pwd((State#state.curwin)#window.buffer);
+pwd(Buf) when atom(Buf) ->
+ case edit_buf:get_filename(Buf) of
+ undefined ->
+ case file:get_cwd() of
+ {ok, Path} ->
+ case lists:last(Path) of
+ $/ ->
+ Path;
+ _ ->
+ Path ++ "/"
+ end;
+ _ ->
+ undefined
+ end;
+ Filename ->
+ filename:dirname(Filename)++"/"
+ end.
+