Class: Everywhere::Dock

Inherits:
Object
  • Object
show all
Defined in:
lib/everywhere/dock.rb,
lib/everywhere/dock/state.rb,
lib/everywhere/dock/footer.rb,
lib/everywhere/dock/screen.rb

Overview

The pinned status bar at the bottom of every dev.

The old key menu was printed once, before the loop, and was gone from the screen within seconds of the Rails server starting to log. The dock keeps the keymap — and what each target is actually doing — permanently visible, while the logs scroll normally above it.

It installs itself as Console's sink, so every write in the process (CLI chrome, relayed server output, filtered build output) is framed by an erase and a repaint. See Dock::Screen for why it repaints rather than reserving rows with a scroll region.

Defined Under Namespace

Modules: Footer Classes: Null, Screen, State

Constant Summary collapse

TICK =
0.2
KEYS =
[
  ["d", "desktop"],
  ["i", "iOS"],
  ["a", "Android"],
  ["b", "browser"],
  ["l", "logs"],
  ["r", "restart"],
  ["?", "help"],
  ["q", "quit"]
].freeze
HELP =
[
  ["d", "relaunch the desktop shell (opens it if it isn't running)"],
  ["i", "rebuild, reinstall and relaunch on the iOS Simulator"],
  ["a", "rebuild, reinstall and relaunch on the Android emulator"],
  ["b", "open the app in your default browser"],
  ["l", "start/stop streaming iOS Simulator logs"],
  ["r", "restart the dev server only"],
  ["?", "hide this help"],
  ["q", "quit (Ctrl-C works too)"]
].freeze
MIN_ROWS =

A dock needs room to be worth having, and a terminal that understands cursor movement. NO_COLOR is deliberately NOT a disqualifier: it governs SGR, not cursor control, and the footer's glyphs carry the meaning on their own.

12
MIN_COLS =
40

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(targets, io: $stdout, keys: true, size: nil) ⇒ Dock

Returns a new instance of Dock.



68
69
70
71
72
73
74
75
76
# File 'lib/everywhere/dock.rb', line 68

def initialize(targets, io: $stdout, keys: true, size: nil)
  @state = State.new(targets)
  @screen = Screen.new(io: io, size: size)
  @keys = keys
  @help = false
  @frame = 0
  @dirty = true
  @open = false
end

Instance Attribute Details

#stateObject (readonly)

Returns the value of attribute state.



78
79
80
# File 'lib/everywhere/dock.rb', line 78

def state
  @state
end

Class Method Details

.drawable?(io, size) ⇒ Boolean

Returns:

  • (Boolean)


59
60
61
62
63
64
65
66
# File 'lib/everywhere/dock.rb', line 59

def self.drawable?(io, size)
  return false if ENV["EVERY_NO_DOCK"]
  return false unless io.respond_to?(:tty?) && io.tty?
  return false if ENV["TERM"].to_s.empty? || ENV["TERM"] == "dumb"

  rows, cols = size || Screen.new(io: io).size
  rows >= MIN_ROWS && cols >= MIN_COLS
end

.open(targets, io: $stdout, keys: true, size: nil) ⇒ Object



53
54
55
56
57
# File 'lib/everywhere/dock.rb', line 53

def self.open(targets, io: $stdout, keys: true, size: nil)
  return Null.new(targets, keys: keys) unless drawable?(io, size)

  new(targets, io: io, keys: keys, size: size).tap(&:install)
end

Instance Method Details

#announce_keysObject

The dock draws the keymap permanently, so there is nothing to announce. Null overrides this to print the one-shot hint the old command used.



110
# File 'lib/everywhere/dock.rb', line 110

def announce_keys = nil

#clearObject

Wipe the footer and leave it wiped — for stderr, or an interactive child that needs the screen to itself.



133
# File 'lib/everywhere/dock.rb', line 133

def clear = @screen.clear

#closeObject



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/everywhere/dock.rb', line 135

def close
  return unless @open

  # Order matters: drop the flag so the ticker stops drawing, let it finish
  # the frame it may be mid-way through, and only then unhook and restore.
  @open = false
  @ticker&.join(TICK * 2)
  @ticker&.kill
  Console.dock = nil
  Console.synchronize { @screen.close }
end

#detail(key, text) ⇒ Object



97
98
99
100
# File 'lib/everywhere/dock.rb', line 97

def detail(key, text)
  @state.detail(key, text)
  @dirty = true
end

#emit(text) ⇒ Object

Called by Console, which already holds the lock. Content must end at column 1, or the erase math for the next frame is off by a row.



116
117
118
119
120
# File 'lib/everywhere/dock.rb', line 116

def emit(text)
  body = text.end_with?("\n") ? text : "#{text}\n"
  @screen.frame(body, footer_rows)
  @dirty = false
end

#installObject



80
81
82
83
84
85
86
87
# File 'lib/everywhere/dock.rb', line 80

def install
  @open = true
  @screen.open
  Console.dock = self
  trap_resize
  start_ticker
  self
end

#refreshObject



122
123
124
125
126
127
128
129
# File 'lib/everywhere/dock.rb', line 122

def refresh
  Console.synchronize do
    next unless @open

    @screen.frame("", footer_rows)
    @dirty = false
  end
end

#set(key, state, detail: nil) ⇒ Object

--- state --------------------------------------------------------------



91
92
93
94
95
# File 'lib/everywhere/dock.rb', line 91

def set(key, state, detail: nil)
  @state.set(key, state, detail: detail)
  @dirty = true
  refresh
end

#toggle_helpObject



102
103
104
105
106
# File 'lib/everywhere/dock.rb', line 102

def toggle_help
  @help = !@help
  @dirty = true
  refresh
end