Class: Chamomile::LogView

Inherits:
Object
  • Object
show all
Defined in:
lib/chamomile/components/log_view.rb

Overview

A scrollable log view with auto-scroll and SQL/error highlighting. Designed for streaming log panels.

Usage:

@log_view = Chamomile::LogView.new(max_lines: 1000)
@log_view.push("GET /users 200 10ms")
@log_view.push("SELECT * FROM users")
rendered = @log_view.render(width: 80, height: 20)

Constant Summary collapse

SQL_PATTERN =
/\b(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)\b/i
ERROR_PATTERN =
/(Error|Exception|FATAL|Errno)/
REQUEST_PATTERN =
%r{\b(GET|POST|PUT|PATCH|DELETE)\s+/}
SQL_COLOR =
"\e[38;5;33m"
ERROR_COLOR =
"\e[38;5;196m"
REQUEST_COLOR =
"\e[38;5;34m"
DIM_COLOR =
"\e[38;5;240m"
RESET =
"\e[0m"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_lines: 1000) ⇒ LogView

Returns a new instance of LogView.



25
26
27
28
29
30
31
32
# File 'lib/chamomile/components/log_view.rb', line 25

def initialize(max_lines: 1000)
  @lines         = []
  @max_lines     = max_lines
  @scroll_offset = 0
  @paused        = false
  @line_count    = 0
  @mutex         = Mutex.new
end

Instance Attribute Details

#line_countObject (readonly)

Returns the value of attribute line_count.



23
24
25
# File 'lib/chamomile/components/log_view.rb', line 23

def line_count
  @line_count
end

#pausedObject (readonly)

Returns the value of attribute paused.



23
24
25
# File 'lib/chamomile/components/log_view.rb', line 23

def paused
  @paused
end

Instance Method Details

#at_bottom?Boolean

Returns:

  • (Boolean)


65
# File 'lib/chamomile/components/log_view.rb', line 65

def at_bottom? = @scroll_offset.zero?

#clearObject



95
96
97
98
99
100
101
# File 'lib/chamomile/components/log_view.rb', line 95

def clear
  @mutex.synchronize do
    @lines.clear
    @scroll_offset = 0
    @line_count = 0
  end
end

#pause!Object



46
# File 'lib/chamomile/components/log_view.rb', line 46

def pause!  = (@paused = true)

#push(line) ⇒ Object

Thread-safe line push. Called from stream command threads.



35
36
37
38
39
40
41
42
43
44
# File 'lib/chamomile/components/log_view.rb', line 35

def push(line)
  @mutex.synchronize do
    return if @paused

    @lines << highlight(line.chomp)
    @lines.shift if @lines.size > @max_lines
    @line_count += 1
    @scroll_offset += 1 if @scroll_offset.positive?
  end
end

#render(width:, height:) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/chamomile/components/log_view.rb', line 67

def render(width:, height:)
  @mutex.synchronize do
    return "" if height <= 0

    # Clamp scroll so we can't scroll past showing the first lines
    max_scroll = [@lines.size - height, 0].max
    effective_offset = [@scroll_offset, max_scroll].min

    visible = if effective_offset.zero?
                @lines.last(height)
              else
                bottom_idx = @lines.size - effective_offset
                start_idx  = [bottom_idx - height, 0].max
                @lines[start_idx...bottom_idx] || []
              end

    padded = visible.map { |l| truncate_ansi(l, width) }
    padded.fill("", padded.size...height)

    if effective_offset.positive? && height > 1
      status = " #{DIM_COLOR}#{effective_offset} newer lines below (G to jump to bottom)#{RESET} "
      padded[-1] = status
    end

    padded.join("\n")
  end
end

#resume!Object



47
# File 'lib/chamomile/components/log_view.rb', line 47

def resume! = (@paused = false)

#scroll_down(n = 1) ⇒ Object



55
56
57
58
59
# File 'lib/chamomile/components/log_view.rb', line 55

def scroll_down(n = 1)
  @mutex.synchronize do
    @scroll_offset = [@scroll_offset - n, 0].max
  end
end

#scroll_to_bottomObject



61
62
63
# File 'lib/chamomile/components/log_view.rb', line 61

def scroll_to_bottom
  @mutex.synchronize { @scroll_offset = 0 }
end

#scroll_up(n = 1) ⇒ Object



49
50
51
52
53
# File 'lib/chamomile/components/log_view.rb', line 49

def scroll_up(n = 1)
  @mutex.synchronize do
    @scroll_offset = (@scroll_offset + n).clamp(0, [@lines.size - 1, 0].max)
  end
end