rails_server_hotkeys 1.1.1
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 +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +122 -0
- data/lib/rails_server_hotkeys/actions.rb +94 -0
- data/lib/rails_server_hotkeys/boot_hook.rb +28 -0
- data/lib/rails_server_hotkeys/color.rb +29 -0
- data/lib/rails_server_hotkeys/listener.rb +34 -0
- data/lib/rails_server_hotkeys/railtie.rb +18 -0
- data/lib/rails_server_hotkeys/terminal.rb +52 -0
- data/lib/rails_server_hotkeys/version.rb +3 -0
- data/lib/rails_server_hotkeys.rb +76 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dcf7eece0e781456e10984440dacaa89d63f1c8a2d5e253b3e46edbb8777bd36
|
|
4
|
+
data.tar.gz: 25f0dc57f124910142dfef2d37789e772faf26b04ea00c564bb3adfaa2e84072
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bbd24268025046933f74dc66a92a6359c79253cb39b33dbf3eac561ec91619c0b5931fe90e36a17f11a9d393313610395a33b30af37600101ec5cfd88680ddf4
|
|
7
|
+
data.tar.gz: 8c259bab6875bdbeb776e5ee0e5ec89c46abd65ca6843d4bbeece9c4513cba5687e00bfec2d55451b78b9af90728f158f0b756f01c492467ecba2845090e9163
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.1.1] - 2026-10-31
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Gem metadata: source code, changelog and bug tracker links; MFA required to publish.
|
|
13
|
+
- `CHANGELOG.md` is now included in the gem package.
|
|
14
|
+
|
|
15
|
+
## [1.1.0] - 2026-10-31
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Hotkeys for `rails server`: `r` restart, `c` clear, `o` open browser, `h`/`?` help, `q` quit.
|
|
19
|
+
- `RailsServerHotkeys.register` / `unregister` for custom hotkeys.
|
|
20
|
+
- `RailsServerHotkeys.color` to customize message color (ANSI name, 0-255, hex or `nil`); honors `NO_COLOR`.
|
|
21
|
+
- `config.hotkeys.enabled`, `config.hotkeys.url` and `RAILS_SERVER_HOTKEYS=false` to configure or disable.
|
|
22
|
+
- `o` opens the port the server is actually listening on.
|
|
23
|
+
- Banner shown after Puma's "Use Ctrl-C to stop".
|
|
24
|
+
|
|
25
|
+
[Unreleased]: https://github.com/samuelpismel/rails_server_hotkeys/compare/v1.1.1...HEAD
|
|
26
|
+
[1.1.1]: https://github.com/samuelpismel/rails_server_hotkeys/compare/v1.1.0...v1.1.1
|
|
27
|
+
[1.1.0]: https://github.com/samuelpismel/rails_server_hotkeys/releases/tag/v1.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Gavassi Pismel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# rails_server_hotkeys
|
|
2
|
+
|
|
3
|
+
Expo-style keyboard shortcuts for `bin/rails server`. Press `r` to restart the server — no more `touch tmp/restart.txt` in another terminal.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
=> Booting Puma
|
|
7
|
+
...
|
|
8
|
+
› Press r to restart, h for help
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# Gemfile
|
|
15
|
+
gem "rails_server_hotkeys", group: :development
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That's it. Run `bin/rails s` and press a key.
|
|
19
|
+
|
|
20
|
+
| Key | Action |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `r` | restart the server (same as `bin/rails restart`) |
|
|
23
|
+
| `c` | clear the terminal |
|
|
24
|
+
| `o` | open the app in the browser |
|
|
25
|
+
| `h` / `?` | list hotkeys |
|
|
26
|
+
| `q` | quit the server |
|
|
27
|
+
|
|
28
|
+
## Custom hotkeys
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# config/initializers/hotkeys.rb
|
|
32
|
+
if defined?(RailsServerHotkeys)
|
|
33
|
+
RailsServerHotkeys.register("m", "run db:migrations") { system("bin/rails db:migrate") }
|
|
34
|
+
RailsServerHotkeys.register("t", "run custom task") { system("bin/rails myapp:do_something") }
|
|
35
|
+
RailsServerHotkeys.register("e", "run system echo") { system("echo 'custom message'") }
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Registering an existing key replaces it; `RailsServerHotkeys.unregister("o")` removes one.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# config/environments/development.rb
|
|
45
|
+
config.hotkeys.enabled = false # turn it off
|
|
46
|
+
config.hotkeys.url = "https://myapp.test" # URL opened by `o` (default: localhost on the port the server is listening on)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or disable for a single run: `RAILS_SERVER_HOTKEYS=false bin/rails s`.
|
|
50
|
+
|
|
51
|
+
### Color
|
|
52
|
+
|
|
53
|
+
Messages are light blue by default. Change it in an initializer:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
# config/initializers/hotkeys.rb
|
|
57
|
+
if defined?(RailsServerHotkeys)
|
|
58
|
+
RailsServerHotkeys.color = :green # black, red, green, yellow, blue, magenta, cyan, white,
|
|
59
|
+
# or light_* of any of them (the default is :green)
|
|
60
|
+
RailsServerHotkeys.color = 208 # 256-color palette (0-255)
|
|
61
|
+
RailsServerHotkeys.color = "#ff8800" # true color (hex)
|
|
62
|
+
RailsServerHotkeys.color = nil # no color
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
An invalid value raises `ArgumentError` at boot. Colors are skipped when the output isn't a terminal or `NO_COLOR` is set.
|
|
67
|
+
|
|
68
|
+
## When is it active?
|
|
69
|
+
|
|
70
|
+
Only for `rails server`, in the `development` environment, when stdin is a terminal. Console, rake, tests, jobs and non-interactive runs (Docker without `-it`, CI) are untouched.
|
|
71
|
+
|
|
72
|
+
## Caveats
|
|
73
|
+
|
|
74
|
+
- **Restart needs Puma's `tmp_restart` plugin.** It's in the `config/puma.rb` generated by Rails; if yours lacks it, add `plugin :tmp_restart`.
|
|
75
|
+
- **`bin/dev` (foreman) doesn't forward stdin**, so hotkeys can't work there. Run `bin/rails s` directly, or use overmind and `overmind connect web`.
|
|
76
|
+
- **Unix/macOS only** (uses `stty`). On Windows the gem does nothing.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
bundle install
|
|
82
|
+
bundle exec rake test
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Trying it in a dummy app
|
|
86
|
+
|
|
87
|
+
The unit tests run without Rails, but hotkeys only show up for real under `rails server` in a terminal. To try your changes by hand, create a throwaway app next to the gem:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
# Needs the rails and bundler gems (`gem install rails bundler`)
|
|
91
|
+
cd ..
|
|
92
|
+
rails new hotkeys-dummy --minimal --skip-git
|
|
93
|
+
cd hotkeys-dummy
|
|
94
|
+
|
|
95
|
+
# Point the app at your local copy of the gem
|
|
96
|
+
echo 'gem "rails_hotkeys", path: "../restart-rails-hotkeys", group: :development' >> Gemfile
|
|
97
|
+
bundle install
|
|
98
|
+
|
|
99
|
+
bin/rails s
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Adjust the `path:` if your clone has a different folder name.
|
|
103
|
+
|
|
104
|
+
You should see this in light blue, right after Puma's `Use Ctrl-C to stop`:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
› Press r to restart, h for help
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Things to check:
|
|
111
|
+
|
|
112
|
+
- `r` prints `* Restarting...` and the server comes back up
|
|
113
|
+
- `h` lists the hotkeys, `c` clears the screen
|
|
114
|
+
- `o` opens the browser on the port the server is listening on (try `bin/rails s -p 4000`)
|
|
115
|
+
- `q` or Ctrl+C stops the server, and your shell echoes what you type again
|
|
116
|
+
- `bin/rails s < /dev/null` boots normally with no banner (no TTY, hotkeys off)
|
|
117
|
+
|
|
118
|
+
Because the gem is loaded through `path:`, edits to it take effect on the next restart: press `r` instead of stopping the server. You don't need to run `bundle install` again unless you change the gemspec.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "rbconfig"
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module RailsServerHotkeys
|
|
6
|
+
# Built-in hotkeys. They resolve paths and URLs when pressed, so they work no
|
|
7
|
+
# matter when the app finishes booting.
|
|
8
|
+
module Actions
|
|
9
|
+
class << self
|
|
10
|
+
attr_writer :root, :url, :output
|
|
11
|
+
|
|
12
|
+
def root
|
|
13
|
+
@root || (defined?(Rails) && Rails.respond_to?(:root) && Rails.root) || Dir.pwd
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def url
|
|
17
|
+
@url || "http://localhost:#{listening_ports.first || ENV.fetch("PORT", 3000)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Ports this process is actually listening on, however they were chosen
|
|
21
|
+
# (-p, PORT, config/puma.rb). Resolved on demand, so restarts are covered.
|
|
22
|
+
def listening_ports
|
|
23
|
+
ObjectSpace.each_object(TCPServer).filter_map do |server|
|
|
24
|
+
next if server.closed?
|
|
25
|
+
|
|
26
|
+
server.local_address.ip_port
|
|
27
|
+
rescue IOError, SystemCallError
|
|
28
|
+
nil
|
|
29
|
+
end.uniq.sort
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def output
|
|
33
|
+
@output || $stdout
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def banner
|
|
37
|
+
colorize("\n› Press r to restart, h for help")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Uses RailsServerHotkeys.color, only on a real terminal (and respecting NO_COLOR).
|
|
41
|
+
def colorize(text)
|
|
42
|
+
code = Color.sgr(RailsServerHotkeys.color)
|
|
43
|
+
return text if code.nil? || ENV["NO_COLOR"] || !(output.respond_to?(:tty?) && output.tty?)
|
|
44
|
+
|
|
45
|
+
"\e[#{code}m#{text}\e[0m"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Same as `bin/rails restart`; picked up by Puma's tmp_restart plugin.
|
|
49
|
+
def restart
|
|
50
|
+
path = File.join(root.to_s, "tmp", "restart.txt")
|
|
51
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
52
|
+
FileUtils.touch(path)
|
|
53
|
+
output.puts colorize("› Restarting...")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def clear
|
|
57
|
+
output.print "\e[H\e[2J\e[3J"
|
|
58
|
+
output.puts banner
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def open_browser
|
|
62
|
+
command = case RbConfig::CONFIG["host_os"]
|
|
63
|
+
when /darwin/ then "open"
|
|
64
|
+
else "xdg-open"
|
|
65
|
+
end
|
|
66
|
+
output.puts colorize("› Opening #{url}")
|
|
67
|
+
pid = Process.spawn(command, url, out: File::NULL, err: File::NULL)
|
|
68
|
+
Process.detach(pid)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def help
|
|
72
|
+
output.puts colorize("› Hotkeys:")
|
|
73
|
+
RailsServerHotkeys.hotkeys.each_value do |hotkey|
|
|
74
|
+
output.puts colorize(" #{hotkey.key} #{hotkey.description}")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def quit
|
|
79
|
+
Process.kill(:INT, Process.pid)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def register_defaults
|
|
83
|
+
RailsServerHotkeys.register("r", "restart the server") { restart }
|
|
84
|
+
RailsServerHotkeys.register("c", "clear the terminal") { clear }
|
|
85
|
+
RailsServerHotkeys.register("o", "open the app in the browser") { open_browser }
|
|
86
|
+
RailsServerHotkeys.register("h", "show this help") { help }
|
|
87
|
+
RailsServerHotkeys.register("?", "show this help") { help }
|
|
88
|
+
RailsServerHotkeys.register("q", "quit the server") { quit }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
RailsServerHotkeys::Actions.register_defaults
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module RailsServerHotkeys
|
|
2
|
+
# Runs a block once the server has booted, so output lands after Puma's
|
|
3
|
+
# "Use Ctrl-C to stop" instead of in the middle of its startup log.
|
|
4
|
+
module BootHook
|
|
5
|
+
# Puma >= 6.5 renamed fire_on_booted! to fire_after_booted!.
|
|
6
|
+
PUMA_EVENTS = %i[fire_after_booted! fire_on_booted!].freeze
|
|
7
|
+
|
|
8
|
+
def self.call(&block)
|
|
9
|
+
load_puma_events
|
|
10
|
+
event = defined?(Puma::Events) && PUMA_EVENTS.find { |name| Puma::Events.method_defined?(name) }
|
|
11
|
+
return block.call unless event
|
|
12
|
+
|
|
13
|
+
Puma::Events.prepend(Module.new do
|
|
14
|
+
define_method(event) do |*args, **kwargs|
|
|
15
|
+
super(*args, **kwargs).tap { block.call }
|
|
16
|
+
end
|
|
17
|
+
end)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# When Rails runs server blocks, the Puma handler is chosen but
|
|
21
|
+
# Puma::Events isn't required yet.
|
|
22
|
+
def self.load_puma_events
|
|
23
|
+
require "puma/events" if defined?(Puma) && !defined?(Puma::Events)
|
|
24
|
+
rescue LoadError
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module RailsServerHotkeys
|
|
2
|
+
# Turns a user-facing color setting into an ANSI SGR code.
|
|
3
|
+
module Color
|
|
4
|
+
NAMES = %i[black red green yellow blue magenta cyan white].freeze
|
|
5
|
+
|
|
6
|
+
CODES = NAMES.each_with_index.each_with_object({}) do |(name, index), codes|
|
|
7
|
+
codes[name] = (30 + index).to_s
|
|
8
|
+
codes[:"light_#{name}"] = (90 + index).to_s
|
|
9
|
+
end.freeze
|
|
10
|
+
|
|
11
|
+
HEX = /\A#?(\h{2})(\h{2})(\h{2})\z/
|
|
12
|
+
|
|
13
|
+
# :green / "light_green" => named color, 0..255 => 256-color palette,
|
|
14
|
+
# "#ff8800" => true color, nil / false => no color.
|
|
15
|
+
def self.sgr(color)
|
|
16
|
+
case color
|
|
17
|
+
when nil, false then nil
|
|
18
|
+
when Integer
|
|
19
|
+
"38;5;#{color}" if color.between?(0, 255)
|
|
20
|
+
when Symbol, String
|
|
21
|
+
CODES[color.to_sym] || (match = HEX.match(color.to_s)) && "38;2;#{match.captures.map(&:hex).join(";")}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.valid?(color)
|
|
26
|
+
color.nil? || color == false || !sgr(color).nil?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module RailsServerHotkeys
|
|
2
|
+
# Reads keys from +input+ and runs the matching hotkey. Unknown keys are ignored.
|
|
3
|
+
class Listener
|
|
4
|
+
def initialize(input:, output:, hotkeys:)
|
|
5
|
+
@input = input
|
|
6
|
+
@output = output
|
|
7
|
+
@hotkeys = hotkeys
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def start
|
|
11
|
+
@thread = Thread.new { run }
|
|
12
|
+
@thread.name = "rails_server_hotkeys"
|
|
13
|
+
@thread.report_on_exception = false
|
|
14
|
+
@thread
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
while (char = @input.getc)
|
|
19
|
+
handle(char)
|
|
20
|
+
end
|
|
21
|
+
rescue IOError, SystemCallError
|
|
22
|
+
# stdin closed; nothing left to listen to
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handle(char)
|
|
26
|
+
hotkey = @hotkeys[char] || @hotkeys[char.downcase]
|
|
27
|
+
return unless hotkey
|
|
28
|
+
|
|
29
|
+
hotkey.action.call
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
@output.puts "[rails_server_hotkeys] '#{char}' failed: #{e.class}: #{e.message}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "rails/railtie"
|
|
2
|
+
|
|
3
|
+
module RailsServerHotkeys
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
config.hotkeys = ActiveSupport::OrderedOptions.new
|
|
6
|
+
config.hotkeys.enabled = true
|
|
7
|
+
config.hotkeys.url = nil
|
|
8
|
+
|
|
9
|
+
# Runs only for `rails server`; never for console, rake, tests or jobs.
|
|
10
|
+
server do
|
|
11
|
+
options = Rails.application.config.hotkeys
|
|
12
|
+
next unless RailsServerHotkeys.enabled?(rails_env: Rails.env, config_enabled: options.enabled)
|
|
13
|
+
|
|
14
|
+
RailsServerHotkeys::Actions.url = options.url if options.url
|
|
15
|
+
RailsServerHotkeys.start
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module RailsServerHotkeys
|
|
2
|
+
# Puts the TTY in "cbreak" mode: keys are delivered one at a time and not
|
|
3
|
+
# echoed, while output processing (OPOST) and signals (ISIG, so Ctrl+C) stay
|
|
4
|
+
# untouched. IO#raw would disable OPOST and make the server logs staircase.
|
|
5
|
+
class Terminal
|
|
6
|
+
# Puma restarts by exec'ing a new process, which skips at_exit. Give the
|
|
7
|
+
# terminal back right before any exec so the new process sees (and saves)
|
|
8
|
+
# the user's original settings.
|
|
9
|
+
module RestoreBeforeExec
|
|
10
|
+
def exec(*args)
|
|
11
|
+
RailsServerHotkeys::Terminal.current&.restore!
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :current
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(input)
|
|
21
|
+
@input = input
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enable!
|
|
25
|
+
@state = stty_state
|
|
26
|
+
return unless @state && stty("-icanon", "-echo", "min", "1")
|
|
27
|
+
|
|
28
|
+
self.class.current = self
|
|
29
|
+
Kernel.singleton_class.prepend(RestoreBeforeExec)
|
|
30
|
+
Process.singleton_class.prepend(RestoreBeforeExec)
|
|
31
|
+
at_exit { restore! }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def restore!
|
|
35
|
+
stty(@state) if @state
|
|
36
|
+
@state = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def stty_state
|
|
42
|
+
state = IO.popen(%w[stty -g], in: @input, err: File::NULL, &:read).strip
|
|
43
|
+
state.empty? ? nil : state
|
|
44
|
+
rescue SystemCallError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stty(*args)
|
|
49
|
+
system("stty", *args, in: @input, err: File::NULL)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "rails_server_hotkeys/version"
|
|
2
|
+
require "rails_server_hotkeys/terminal"
|
|
3
|
+
require "rails_server_hotkeys/listener"
|
|
4
|
+
require "rails_server_hotkeys/boot_hook"
|
|
5
|
+
require "rails_server_hotkeys/color"
|
|
6
|
+
|
|
7
|
+
module RailsServerHotkeys
|
|
8
|
+
Hotkey = Struct.new(:key, :description, :action)
|
|
9
|
+
DEFAULT_COLOR = :green
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# Registers a hotkey. Registering an existing key replaces it.
|
|
13
|
+
#
|
|
14
|
+
# RailsServerHotkeys.register("m", "Run migrations") { system("bin/rails db:migrate") }
|
|
15
|
+
def register(key, description, &action)
|
|
16
|
+
raise ArgumentError, "hotkey must be a single character" unless key.is_a?(String) && key.length == 1
|
|
17
|
+
raise ArgumentError, "a block is required" unless action
|
|
18
|
+
|
|
19
|
+
hotkeys[key] = Hotkey.new(key, description, action)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def unregister(key)
|
|
23
|
+
hotkeys.delete(key)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def hotkeys
|
|
27
|
+
@hotkeys ||= {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Color of the gem's messages.
|
|
31
|
+
#
|
|
32
|
+
# RailsServerHotkeys.color = :green # any of Color::CODES
|
|
33
|
+
# RailsServerHotkeys.color = 208 # 256-color palette
|
|
34
|
+
# RailsServerHotkeys.color = "#ff8800" # true color
|
|
35
|
+
# RailsServerHotkeys.color = nil # no color
|
|
36
|
+
def color
|
|
37
|
+
defined?(@color) ? @color : DEFAULT_COLOR
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def color=(value)
|
|
41
|
+
unless Color.valid?(value)
|
|
42
|
+
raise ArgumentError, "invalid color #{value.inspect}; use one of #{Color::CODES.keys.join(", ")}, " \
|
|
43
|
+
"an integer 0-255, a hex string like \"#ff8800\", or nil"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@color = value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reset_color
|
|
50
|
+
remove_instance_variable(:@color) if defined?(@color)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Whether hotkeys should run for this process.
|
|
54
|
+
def enabled?(input: $stdin, env: ENV, rails_env: nil, config_enabled: true)
|
|
55
|
+
return false unless config_enabled
|
|
56
|
+
return false if %w[0 false off].include?(env["RAILS_SERVER_HOTKEYS"].to_s.downcase)
|
|
57
|
+
return false if Gem.win_platform?
|
|
58
|
+
return false if rails_env && rails_env != "development"
|
|
59
|
+
|
|
60
|
+
input.respond_to?(:tty?) && input.tty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def start(input: $stdin, output: $stdout)
|
|
64
|
+
return if @listener
|
|
65
|
+
|
|
66
|
+
Terminal.new(input).enable!
|
|
67
|
+
@listener = Listener.new(input: input, output: output, hotkeys: hotkeys)
|
|
68
|
+
@listener.start
|
|
69
|
+
BootHook.call { output.puts Actions.banner }
|
|
70
|
+
@listener
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
require "rails_server_hotkeys/actions"
|
|
76
|
+
require "rails_server_hotkeys/railtie" if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_server_hotkeys
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Samuel Gavassi Pismel
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
description: 'Adds interactive hotkeys to the Rails development server: r to restart,
|
|
27
|
+
c to clear, o to open the browser, q to quit, and your own.'
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- CHANGELOG.md
|
|
33
|
+
- LICENSE.txt
|
|
34
|
+
- README.md
|
|
35
|
+
- lib/rails_server_hotkeys.rb
|
|
36
|
+
- lib/rails_server_hotkeys/actions.rb
|
|
37
|
+
- lib/rails_server_hotkeys/boot_hook.rb
|
|
38
|
+
- lib/rails_server_hotkeys/color.rb
|
|
39
|
+
- lib/rails_server_hotkeys/listener.rb
|
|
40
|
+
- lib/rails_server_hotkeys/railtie.rb
|
|
41
|
+
- lib/rails_server_hotkeys/terminal.rb
|
|
42
|
+
- lib/rails_server_hotkeys/version.rb
|
|
43
|
+
homepage: https://github.com/samuelpismel/rails_server_hotkeys
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata:
|
|
47
|
+
homepage_uri: https://github.com/samuelpismel/rails_server_hotkeys
|
|
48
|
+
source_code_uri: https://github.com/samuelpismel/rails_server_hotkeys
|
|
49
|
+
changelog_uri: https://github.com/samuelpismel/rails_server_hotkeys/blob/master/CHANGELOG.md
|
|
50
|
+
bug_tracker_uri: https://github.com/samuelpismel/rails_server_hotkeys/issues
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.0'
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 4.0.7
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Keyboard hotkeys for `rails server` (press r to restart).
|
|
69
|
+
test_files: []
|