Skip to content

Commit e121a8b

Browse files
committed
Viewer: prevent paging after the last
If you press next too many times or try to jump to a large numbered page, you can view past the end of the log. This prevents going past the last page. This also fixes a couple off by one calculation errors on the pagination: 1. When logs exactly filled all pages, there was an extra blank page 2. The last log on a page was repeated as the first log on the next
1 parent 5595314 commit e121a8b

File tree

1 file changed

+72
-55
lines changed

1 file changed

+72
-55
lines changed

lib/ring_logger/viewer.ex

+72-55
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule RingLogger.Viewer do
1515

1616
@headers ["#", "Level", "Application", "Message", "Timestamp"]
1717
@header_lines 2
18-
@footer_lines 2
18+
@footer_lines 1
1919
@width_of_layout_items 53
2020
@min_log_width 30
2121
@min_log_entries 10
@@ -28,6 +28,8 @@ defmodule RingLogger.Viewer do
2828
running: true,
2929
last_cmd_string: nil,
3030
current_page: 0,
31+
last_page: 0,
32+
per_page: 0,
3133
screen_dims: %{w: 0, h: 0},
3234
lowest_log_level: nil,
3335
before_boot: true,
@@ -52,22 +54,18 @@ defmodule RingLogger.Viewer do
5254

5355
IO.puts("Starting RingLogger Viewer...")
5456

55-
starting_state = @init_state |> get_log_snapshot()
56-
57-
draw(starting_state)
58-
59-
:ok
57+
@init_state |> get_log_snapshot() |> loop()
6058
end
6159

6260
#### Drawing and IO Functions
61+
defp loop(%{running: false} = _state) do
62+
:ok
63+
end
6364

64-
defp draw(state) do
65+
defp loop(state) do
6566
screen_dims = get_screen_dims()
66-
new_state = %{state | screen_dims: screen_dims} |> do_draw()
6767

68-
if new_state.running do
69-
draw(new_state)
70-
end
68+
state |> update_dimensions(screen_dims) |> do_draw() |> loop()
7169
end
7270

7371
defp get_screen_dims() do
@@ -77,10 +75,34 @@ defmodule RingLogger.Viewer do
7775
%{w: cols, h: rows}
7876
end
7977

78+
defp update_dimensions(%{screen_dims: screen_dims} = state, screen_dims) do
79+
# No changes
80+
state
81+
end
82+
83+
defp update_dimensions(state, screen_dims) do
84+
%{state | screen_dims: screen_dims} |> recalculate_pagination()
85+
end
86+
87+
defp recalculate_pagination(state) do
88+
index = state.per_page * state.current_page
89+
90+
per_page = state.screen_dims.h - (@header_lines + @footer_lines)
91+
page_count = ceil(length(state.raw_logs) / per_page)
92+
last_page = page_count - 1
93+
94+
current_page = div(index, per_page)
95+
96+
%{
97+
state
98+
| per_page: per_page,
99+
current_page: current_page,
100+
last_page: last_page
101+
}
102+
end
103+
80104
defp do_draw(state) do
81-
filtered_logs =
82-
state.raw_logs
83-
|> paginate_logs(state)
105+
filtered_logs = current_page(state)
84106

85107
[
86108
reset_screen(),
@@ -108,8 +130,7 @@ defmodule RingLogger.Viewer do
108130
end
109131

110132
defp compute_prompt(state) do
111-
per_page = state.screen_dims.h - (@header_lines + @footer_lines)
112-
prefix = "[#{state.current_page}/#{div(length(state.raw_logs), per_page)}] "
133+
prefix = "[#{state.current_page}/#{state.last_page}] "
113134

114135
level_suffix =
115136
if state.lowest_log_level != nil do
@@ -194,7 +215,7 @@ defmodule RingLogger.Viewer do
194215
split_segment
195216
end
196217

197-
%{state | raw_logs: entries |> apply_log_filters(state)}
218+
%{state | raw_logs: entries |> apply_log_filters(state)} |> recalculate_pagination()
198219
end
199220

200221
defp find_starting_index(entries) do
@@ -211,11 +232,11 @@ defmodule RingLogger.Viewer do
211232
end
212233
end
213234

214-
defp paginate_logs(entries, state) do
215-
per_page = state.screen_dims.h - (@header_lines + @footer_lines)
216-
current_index = state.current_page * per_page
235+
defp current_page(state) do
236+
page_first_index = state.current_page * state.per_page
237+
page_last_index = page_first_index + state.per_page - 1
217238

218-
Enum.slice(entries, current_index..(current_index + per_page))
239+
Enum.slice(state.raw_logs, page_first_index..page_last_index)
219240
end
220241

221242
defp apply_log_filters(entries, state) do
@@ -287,7 +308,7 @@ defmodule RingLogger.Viewer do
287308
end
288309

289310
defp command("b", _cmd_string, state) do
290-
%{state | before_boot: !state.before_boot} |> get_log_snapshot()
311+
%{state | before_boot: !state.before_boot, current_page: 0} |> get_log_snapshot()
291312
end
292313

293314
defp command("l", cmd_string, state) do
@@ -304,22 +325,19 @@ defmodule RingLogger.Viewer do
304325

305326
defp command(_, _cmd_string, state), do: state
306327

328+
defp next_page(%{current_page: p, last_page: p} = state), do: state
307329
defp next_page(%{current_page: n} = state), do: %{state | current_page: n + 1}
308-
defp prev_page(%{current_page: 0} = state), do: %{state | current_page: 0}
330+
defp prev_page(%{current_page: 0} = state), do: state
309331
defp prev_page(%{current_page: n} = state), do: %{state | current_page: n - 1}
310332

311333
defp jump_to_page(cmd_string, state) do
312-
split = String.split(cmd_string)
313-
314-
case length(split) do
315-
1 ->
316-
per_page = state.screen_dims.h - (@header_lines + @footer_lines)
317-
last_page = div(length(state.raw_logs), per_page)
318-
%{state | current_page: last_page}
334+
case String.split(cmd_string) do
335+
[_] ->
336+
%{state | current_page: state.last_page}
319337

320-
2 ->
321-
{page, _} = Integer.parse(Enum.at(split, 1))
322-
%{state | current_page: max(0, page)}
338+
[_, page_string] ->
339+
{page, _} = Integer.parse(page_string)
340+
%{state | current_page: min(max(0, page), state.last_page)}
323341

324342
_ ->
325343
state
@@ -347,41 +365,40 @@ defmodule RingLogger.Viewer do
347365
end
348366

349367
defp set_log_level(cmd_string, state) do
350-
split = String.split(cmd_string)
351-
352-
cond do
353-
length(split) <= 1 ->
368+
case {String.split(cmd_string), state.lowest_log_level} do
369+
{[_cmd], previous} when previous != nil ->
354370
# No args, clear the log level filter
355-
%{state | lowest_log_level: nil}
371+
%{state | lowest_log_level: nil, current_page: 0}
356372

357-
length(split) == 2 and Enum.at(split, 1) in @level_strings ->
373+
{[_cmd, new_level], level} when new_level != level and new_level in @level_strings ->
358374
# 2 args, 2nd arg is a valid log level string
359-
level_str = Enum.at(split, 1)
360-
level_atom = String.to_existing_atom(level_str)
361-
%{state | lowest_log_level: level_atom}
375+
level_atom = String.to_existing_atom(new_level)
376+
%{state | lowest_log_level: level_atom, current_page: 0}
362377

363-
true ->
378+
_ ->
364379
state
365380
end
366381
end
367382

368383
defp add_remove_app(cmd_string, state) do
369-
split = String.split(cmd_string)
370-
371-
cond do
372-
length(split) == 1 ->
373-
%{state | applications_filter: []}
384+
case String.split(cmd_string) do
385+
[_cmd] ->
386+
%{state | applications_filter: [], current_page: 0}
374387

375-
length(split) == 2 ->
376-
app_atom = String.to_existing_atom(Enum.at(split, 1))
388+
[_cmd, app_str] ->
389+
app_atom = String.to_existing_atom(app_str)
377390

378391
if app_atom in state.applications_filter do
379-
%{state | applications_filter: List.delete(state.applications_filter, app_atom)}
392+
%{
393+
state
394+
| applications_filter: List.delete(state.applications_filter, app_atom),
395+
current_page: 0
396+
}
380397
else
381-
%{state | applications_filter: [app_atom | state.applications_filter]}
398+
%{state | applications_filter: [app_atom | state.applications_filter], current_page: 0}
382399
end
383400

384-
true ->
401+
_ ->
385402
state
386403
end
387404
rescue
@@ -393,15 +410,15 @@ defmodule RingLogger.Viewer do
393410

394411
case Regex.compile(str) do
395412
{:ok, expression} ->
396-
%{state | grep_filter: expression}
413+
%{state | grep_filter: expression, current_page: 0}
397414

398415
_ ->
399416
state
400417
end
401418
rescue
402419
# We can't force String.split/2 with parts = 2 to not raise if they only provide 1 arg
403420
# So treat this as the reset condition
404-
_ -> %{state | grep_filter: nil}
421+
_ -> %{state | grep_filter: nil, current_page: 0}
405422
end
406423

407424
defp show_help(state) do

0 commit comments

Comments
 (0)