log_bench 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 566eefe637e2dce1e46718719b178a044fac2c2e4c368b6ee514b93b8cb0c29c
4
- data.tar.gz: 7f4d86b0abddbe4c1a3f694ccf12d1cde3c261b1e87ed940ad0ef21e23cbdf10
3
+ metadata.gz: f5ade69636de4054fff54cdb450391a0c238da4349b60be90b3d8faecd80040b
4
+ data.tar.gz: 7e28cf786b5ba7a54487da0ce6581a24a713eb1449f12127dfd922a7cf294e4f
5
5
  SHA512:
6
- metadata.gz: 82b42f1c7633455d5fc996337a8743cdd8dfaa097b5acccfa115d5b293a0ec527f6aa08f048e62df5b6cb7cd98517fa3b9ce60506b82816d827d13311370ade4
7
- data.tar.gz: c90d5d86ec3442e25b9cdd362a21cf56b6a6268ae7dd5b51270b677d44f79574b3ebfcae1c2e9c2861833ff1108776cfb05c3277e93807492db5edc912734e5e
6
+ metadata.gz: e2199487aae647d6ea13619a6cc8506e65b2e26d569581b16151cbcde0bad0a674241d463b0d04771c378716e0c55ce3547f4a8d17978e8157925e6ecb0f8850
7
+ data.tar.gz: c9d41acd6b3497fcef99e326aed6655f02837c2fc6f27c9b2777aa7a76280a953202086566b29aafe5661011053ab61b05dd41d0c563b88daa399ff17a08d7ed
data/README.md CHANGED
@@ -100,14 +100,14 @@ log_bench log/development.log
100
100
  - **Navigation**: `↑↓` or `jk` to navigate requests
101
101
  - **Pane switching**: `←→` or `hl` to switch between request list and details
102
102
  - **Filtering**: `f` to open filter dialog
103
+ - **Clear filter**: `c` to clear an active filter (press `escape` or `enter` before pressing `c` to clear)
103
104
  - **Sorting**: `s` to cycle through sort options (timestamp, duration, status)
104
105
  - **Auto-scroll**: `a` to toggle auto-scroll mode
105
- - **Clear**: `c` to clear an active filter (press `escape` or `enter` before pressing `c` to clear)
106
106
  - **Quit**: `q` to exit
107
107
 
108
108
  ### Filtering
109
109
 
110
- Press `f` to open the filter dialog.
110
+ Press `f` to open the filter dialog.
111
111
 
112
112
  In the left pane you can filter by:
113
113
 
@@ -233,5 +233,3 @@ This gem is available as open source under the terms of the [MIT License](LICENS
233
233
  ## Support
234
234
 
235
235
  - 🐛 **Bug reports**: [GitHub Issues](https://github.com/silva96/log_bench/issues)
236
- - 💡 **Feature requests**: [GitHub Discussions](https://github.com/silva96/log_bench/discussions)
237
- - 📖 **Documentation**: [GitHub Wiki](https://github.com/silva96/log_bench/wiki)
@@ -17,15 +17,19 @@ module LogBench
17
17
  # UI constants
18
18
  DEFAULT_VISIBLE_HEIGHT = 20
19
19
 
20
- def initialize(state)
20
+ def initialize(state, screen)
21
21
  self.state = state
22
+ self.screen = screen
23
+ self.mouse_handler = MouseHandler.new(state, screen)
22
24
  end
23
25
 
24
26
  def handle_input
25
27
  ch = getch
26
28
  return if ch == -1 || ch.nil?
27
29
 
28
- if filter_mode_active?
30
+ if ch == KEY_MOUSE
31
+ mouse_handler.handle_mouse_input
32
+ elsif filter_mode_active?
29
33
  handle_filter_input(ch)
30
34
  else
31
35
  handle_navigation_input(ch)
@@ -34,7 +38,7 @@ module LogBench
34
38
 
35
39
  private
36
40
 
37
- attr_accessor :state
41
+ attr_accessor :state, :screen, :mouse_handler
38
42
 
39
43
  def filter_mode_active?
40
44
  state.filter_mode || state.detail_filter_mode
@@ -42,18 +46,12 @@ module LogBench
42
46
 
43
47
  def handle_filter_input(ch)
44
48
  case ch
45
- when 10, 13 # Enter - exit filter mode but keep filter text
46
- if state.filter_mode
47
- state.filter_mode = false
48
- elsif state.detail_filter_mode
49
- state.detail_filter_mode = false
50
- end
51
- when 27 # ESC - exit filter mode and clear filter
49
+ when 10, 13, 27
52
50
  state.exit_filter_mode
53
- when Curses::KEY_UP, "k", "K"
51
+ when KEY_UP, "k", "K"
54
52
  state.exit_filter_mode
55
53
  state.navigate_up
56
- when Curses::KEY_DOWN, "j", "J"
54
+ when KEY_DOWN, "j", "J"
57
55
  state.exit_filter_mode
58
56
  state.navigate_down
59
57
  when 127, 8 # Backspace
@@ -95,15 +93,15 @@ module LogBench
95
93
 
96
94
  def handle_navigation_input(ch)
97
95
  case ch
98
- when Curses::KEY_LEFT, "h", "H"
96
+ when KEY_LEFT, "h", "H"
99
97
  state.switch_to_left_pane
100
- when Curses::KEY_RIGHT, "l", "L"
98
+ when KEY_RIGHT, "l", "L"
101
99
  state.switch_to_right_pane
102
100
  when TAB
103
101
  toggle_pane_focus
104
- when Curses::KEY_UP, "k", "K"
102
+ when KEY_UP, "k", "K"
105
103
  handle_up_navigation
106
- when Curses::KEY_DOWN, "j", "J"
104
+ when KEY_DOWN, "j", "J"
107
105
  handle_down_navigation
108
106
  when CTRL_F
109
107
  handle_page_down
@@ -131,6 +129,9 @@ module LogBench
131
129
  state.cycle_sort_mode
132
130
  when "q", "Q", CTRL_C
133
131
  state.stop!
132
+ when "t", "T"
133
+ state.toggle_text_selection_mode
134
+ screen.turn_text_selection_mode(state.text_selection_mode?)
134
135
  when ESC
135
136
  handle_escape
136
137
  end
@@ -55,7 +55,7 @@ module LogBench
55
55
  end
56
56
 
57
57
  def setup_components
58
- self.input_handler = InputHandler.new(state)
58
+ self.input_handler = InputHandler.new(state, screen)
59
59
  self.renderer = Renderer::Main.new(screen, state)
60
60
  end
61
61
 
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LogBench
4
+ module App
5
+ class MouseHandler
6
+ include Curses
7
+
8
+ # UI constants
9
+ DEFAULT_VISIBLE_HEIGHT = 20
10
+
11
+ def initialize(state, screen)
12
+ self.state = state
13
+ self.screen = screen
14
+ end
15
+
16
+ def handle_mouse_input
17
+ with_warnings_suppressed do
18
+ mouse_event = getmouse
19
+
20
+ return unless mouse_event
21
+
22
+ if mouse_event.bstate & BUTTON1_CLICKED != 0
23
+ handle_mouse_click(mouse_event.x, mouse_event.y)
24
+ end
25
+ end
26
+ rescue
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :state, :screen
33
+
34
+ def handle_mouse_click(x, y)
35
+ if click_in_left_pane?(x, y)
36
+ # Switch to left pane if not already focused
37
+ state.switch_to_left_pane unless state.left_pane_focused?
38
+
39
+ # Convert click coordinates to request index
40
+ request_index = click_to_request_index(y)
41
+ return unless request_index
42
+
43
+ # Update selection
44
+ max_index = state.filtered_requests.size - 1
45
+ state.selected = [request_index, max_index].min
46
+ state.auto_scroll = false
47
+ state.adjust_scroll_for_selection(visible_height)
48
+ elsif click_in_right_pane?(x, y)
49
+ # Switch to right pane
50
+ state.switch_to_right_pane unless state.right_pane_focused?
51
+ end
52
+ end
53
+
54
+ def click_in_left_pane?(x, y)
55
+ # Left pane spans from x=0 to panel_width
56
+ # Header takes up first HEADER_HEIGHT lines
57
+ # Request list starts at HEADER_HEIGHT + 1 (accounting for border)
58
+ panel_width = screen.panel_width
59
+ header_height = 5 # Screen::HEADER_HEIGHT
60
+
61
+ x >= 0 && x < panel_width && y > header_height
62
+ end
63
+
64
+ def click_in_right_pane?(x, y)
65
+ # Right pane starts after left panel + border width
66
+ # From Screen: panel_width + PANEL_BORDER_WIDTH
67
+ panel_width = screen.panel_width
68
+ border_width = 3 # Screen::PANEL_BORDER_WIDTH
69
+ header_height = 5 # Screen::HEADER_HEIGHT
70
+
71
+ right_pane_start = panel_width + border_width
72
+
73
+ x >= right_pane_start && y > header_height
74
+ end
75
+
76
+ def click_to_request_index(y)
77
+ # Header takes up first 5 lines
78
+ # Request list has 1 line border at top, then 1 line for column headers
79
+ # So actual request rows start at y = 7 (5 header + 1 border + 1 column header)
80
+ header_height = 5
81
+ list_header_offset = 2 # border + column header
82
+
83
+ row_in_list = y - header_height - list_header_offset
84
+ return nil if row_in_list < 0
85
+
86
+ # Convert to actual request index accounting for scroll
87
+ state.scroll_offset + row_in_list
88
+ end
89
+
90
+ def visible_height
91
+ # Approximate visible height for calculations
92
+ DEFAULT_VISIBLE_HEIGHT
93
+ end
94
+
95
+ def with_warnings_suppressed
96
+ old_verbose = $VERBOSE
97
+ $VERBOSE = nil
98
+ yield
99
+ ensure
100
+ $VERBOSE = old_verbose
101
+ end
102
+ end
103
+ end
104
+ end
@@ -75,14 +75,16 @@ module LogBench
75
75
  header_win.attron(A_DIM) do
76
76
  header_win.addstr("a:Auto-scroll(")
77
77
  header_win.attron(color_pair(3)) { header_win.addstr(state.auto_scroll ? "ON" : "OFF") }
78
- header_win.addstr(") | f:Filter | c:Clear | s:Sort(")
78
+ header_win.addstr(") | f:Filter | c:Clear filter | s:Sort(")
79
79
  header_win.attron(color_pair(3)) { header_win.addstr(state.sort.display_name) }
80
+ header_win.addstr(") | t:Text selection(")
81
+ header_win.attron(color_pair(3)) { header_win.addstr(state.text_selection_mode? ? "ON" : "OFF") }
80
82
  header_win.addstr(") | q:Quit")
81
83
  end
82
84
 
83
85
  header_win.setpos(3, 2)
84
86
  header_win.attron(A_DIM) do
85
- header_win.addstr("←→/hl:Switch Pane | ↑↓/jk:Navigate | g/G:Top/End")
87
+ header_win.addstr("←→/hl:Switch Pane | ↑↓/jk/Click:Navigate | g/G:Top/End")
86
88
  end
87
89
  end
88
90
 
@@ -29,6 +29,7 @@ module LogBench
29
29
  setup_colors
30
30
  clear_screen_immediately
31
31
  setup_windows
32
+ turn_text_selection_mode(false)
32
33
  end
33
34
 
34
35
  def cleanup
@@ -54,6 +55,10 @@ module LogBench
54
55
  Curses.color_pair(n)
55
56
  end
56
57
 
58
+ def turn_text_selection_mode(enabled)
59
+ enabled ? mousemask(0) : mousemask(BUTTON1_CLICKED)
60
+ end
61
+
57
62
  private
58
63
 
59
64
  attr_writer :header_win, :log_win, :panel_width, :detail_win
@@ -4,7 +4,7 @@ module LogBench
4
4
  module App
5
5
  class State
6
6
  attr_reader :main_filter, :sort, :detail_filter
7
- attr_accessor :requests, :auto_scroll, :scroll_offset, :selected, :detail_scroll_offset
7
+ attr_accessor :requests, :auto_scroll, :scroll_offset, :selected, :detail_scroll_offset, :text_selection_mode
8
8
 
9
9
  def initialize
10
10
  self.requests = []
@@ -14,6 +14,7 @@ module LogBench
14
14
  self.running = true
15
15
  self.focused_pane = :left
16
16
  self.detail_scroll_offset = 0
17
+ self.text_selection_mode = false
17
18
  self.main_filter = Filter.new
18
19
  self.detail_filter = Filter.new
19
20
  self.sort = Sort.new
@@ -31,6 +32,14 @@ module LogBench
31
32
  self.auto_scroll = !auto_scroll
32
33
  end
33
34
 
35
+ def toggle_text_selection_mode
36
+ self.text_selection_mode = !text_selection_mode
37
+ end
38
+
39
+ def text_selection_mode?
40
+ text_selection_mode
41
+ end
42
+
34
43
  def clear_filter
35
44
  main_filter.clear
36
45
  self.selected = 0
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LogBench
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: log_bench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamín Silva
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-06 00:00:00.000000000 Z
10
+ date: 2025-06-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -114,6 +114,7 @@ files:
114
114
  - lib/log_bench/app/input_handler.rb
115
115
  - lib/log_bench/app/main.rb
116
116
  - lib/log_bench/app/monitor.rb
117
+ - lib/log_bench/app/mouse_handler.rb
117
118
  - lib/log_bench/app/renderer/ansi.rb
118
119
  - lib/log_bench/app/renderer/details.rb
119
120
  - lib/log_bench/app/renderer/header.rb