termcontroller 0.1.0 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1fad37d234332ef83aa87356c2ce2cb16f0625bc5e53e37db193e2d0f0343fd
4
- data.tar.gz: cad01ac6503432df902fe0c1d204c6698d13088376e23a418c1d7ca5d5709cab
3
+ metadata.gz: ccc884e481a6ba710cb7edbd7edc1b05c20ed49e7f8e81cb0288813472006466
4
+ data.tar.gz: 6738a465db9c70c9d4d67f1626957de5209fbfbee95557cf9115f8734ff970e9
5
5
  SHA512:
6
- metadata.gz: b0d4ba2609f21e798a4ea6798d02f94f7dba23f6011d015568206c219dbccedb8b994c6d66e30fc4b25c6e4ab9c56b7455f12aa1dec856ba5dac4eddcae77e4e
7
- data.tar.gz: 7d4b1b9a57cc196538851e537403e2ff8bafbfeab55d22ad3fccbf70ad27c4df22bc0ac99701c1ffa587af02baeb62f2ac088527bd1655a26f39d7cc9cdee01d
6
+ metadata.gz: d6c8d516c1a855d46833a6ead5e0a28ab2a682b1ce18a8039d301933acb2811d627b65c62b2ecbd0298c8379af98a7502d86a542342fc3c48911f996ab57cfba
7
+ data.tar.gz: 5f313f8f34231eaa7715c0df26e7abd470ee770af60cdbffb4c4dc7f1f8c08132df5e1f4558b713e26a9be307e5f496c51cfd7817efb12c6ac557dbe87d8e5e5
data/.gitignore CHANGED
@@ -6,6 +6,6 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
9
+ *~
10
10
  # rspec failure tracking
11
11
  .rspec_status
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Termcontroller
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/termcontroller`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ A very basic Controller (in the MVC sense) for Ruby terminal
4
+ applications. This was pulled out of my text editor,
5
+ [Re](https://github.com/vidarh/re).
6
6
 
7
7
  ## Installation
8
8
 
@@ -26,15 +26,23 @@ TODO: Write usage instructions here
26
26
 
27
27
  ## Development
28
28
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+ After checking out the repo, run `bin/setup` to install dependencies.
30
+ Then, run `rake spec` to run the tests. You can also run `bin/console`
31
+ for an interactive prompt that will allow you to experiment.
30
32
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
+ To install this gem onto your local machine, run `bundle exec rake
34
+ install`. To release a new version, update the version number in
35
+ `version.rb`, and then run `bundle exec rake release`, which will create
36
+ a git tag for the version, push git commits and tags, and push the `.gem`
37
+ file to [rubygems.org](https://rubygems.org).
32
38
 
33
39
  ## Contributing
34
40
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/termcontroller.
41
+ Bug reports and pull requests are welcome on GitHub at
42
+ https://github.com/vidarh/termcontroller.
36
43
 
37
44
 
38
45
  ## License
39
46
 
40
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
47
+ The gem is available as open source under the terms of the [MIT
48
+ License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,258 @@
1
+ # coding: utf-8
2
+
3
+ require 'io/console'
4
+
5
+ # # Controller
6
+ #
7
+ # This is way too ill defined. The purpose is to be able to have a
8
+ # separate thread handle the keyboard processing asynchronously,
9
+ # reading from the input, and for an application to then be able to
10
+ # call into it to read a command, or to have the controller dispatch
11
+ # the command directly to a specified target object based on the
12
+ # key bindings.
13
+ #
14
+ # It works well enough to e.g. allow temporarily pausing the processing
15
+ # and then dispatching "binding.pry" and re-enable the processing when
16
+ # it returns.
17
+ #
18
+ # FIXME: Split it into a separate gem.
19
+ #
20
+ # FIXME: Should probably treat this as a singleton.
21
+ #
22
+ #
23
+
24
+ module Termcontroller
25
+ class Controller
26
+
27
+ attr_reader :lastcmd,:lastkey,:lastchar
28
+ attr_accessor :mode
29
+
30
+ @@con = IO.console
31
+
32
+ # Pause *any* Controller instance
33
+ @@pause = false
34
+ def self.pause!
35
+ old = @@pause
36
+ @@pause = true
37
+ @@con.cooked do
38
+ yield
39
+ end
40
+ ensure
41
+ @@pause = old
42
+ end
43
+
44
+ def paused?
45
+ @mode == :pause || @@pause
46
+ end
47
+
48
+ def initialize(target, keybindings)
49
+ @target = target
50
+ @keybindings = keybindings
51
+ @buf = ""
52
+ @commands = []
53
+ @mode = :cooked
54
+
55
+ @kb = KeyboardMap.new
56
+ @@con = @con = IO.console
57
+ raise if !@con
58
+
59
+ at_exit do
60
+ cleanup
61
+ end
62
+
63
+ trap("CONT") { resume }
64
+
65
+ @t = Thread.new { readloop }
66
+ end
67
+
68
+ def setup
69
+ STDOUT.print "\e[?2004h" # Enable bracketed paste
70
+ STDOUT.print "\e[?1000h" # Enable mouse reporting
71
+ STDOUT.print "\e[?1006h" # Enable extended reporting
72
+ end
73
+
74
+ def cleanup
75
+ STDOUT.print "\e[?2004l" #Disable bracketed paste
76
+ STDOUT.print "\e[?1000l" #Disable mouse reporting
77
+ end
78
+
79
+ def readloop
80
+ loop do
81
+ if paused?
82
+ sleep(0.05)
83
+ elsif @mode == :cooked
84
+ read_input
85
+ else
86
+ fill_buf
87
+ end
88
+ end
89
+ end
90
+
91
+ def pause
92
+ old = @mode
93
+ @mode = :pause
94
+ sleep(0.1)
95
+ IO.console.cooked!
96
+ yield
97
+ rescue Interrupt
98
+ ensure
99
+ @mode = old
100
+ end
101
+
102
+ def fill_buf(timeout=0.1)
103
+ if paused?
104
+ sleep(0.1)
105
+ Thread.pass
106
+ return
107
+ end
108
+ @con.raw!
109
+ return if !IO.select([$stdin],nil,nil,0.1)
110
+ str = $stdin.read_nonblock(4096)
111
+ str.force_encoding("utf-8")
112
+ @buf << str
113
+ rescue IO::WaitReadable
114
+ end
115
+
116
+ def getc(timeout=0.1)
117
+ if !paused?
118
+ while @buf.empty?
119
+ fill_buf
120
+ end
121
+ @buf.slice!(0) if !paused? && @mode == :cooked
122
+ else
123
+ sleep(0.1)
124
+ Thread.pass
125
+ return nil
126
+ end
127
+ rescue Interrupt
128
+ end
129
+
130
+ def raw
131
+ @mode = :raw
132
+ yield
133
+ rescue Interrupt
134
+ ensure
135
+ @mode = :cooked
136
+ end
137
+
138
+ def read_char
139
+ sleep(0.01) if @buf.empty?
140
+ @buf.slice!(0)
141
+ rescue Interrupt
142
+ end
143
+
144
+ def get_command
145
+ map = @keybindings
146
+ loop do
147
+ c = nil
148
+ char = getc
149
+ return nil if !char
150
+
151
+ c1 = Array(@kb.call(char)).first
152
+ c = map[c1.to_sym] if c1
153
+
154
+ if c.nil? && c1.kind_of?(String)
155
+ return [:insert_char, c1]
156
+ end
157
+
158
+ if c.nil?
159
+ if c1
160
+ @lastchar = c1.to_sym
161
+ args = c1.respond_to?(:args) ? c1.args : []
162
+ return Array(c1.to_sym).concat(args || [])
163
+ else
164
+ @lastchar = char.inspect
165
+ return nil
166
+ end
167
+ end
168
+
169
+ if c.kind_of?(Hash)
170
+ map = c
171
+ else
172
+ @lastchar = c1.to_sym.to_s.split("_").join(" ")
173
+ @lastchar += " (#{c.to_s})" if c.to_s != @lastchar
174
+ return c
175
+ end
176
+ end
177
+ end
178
+
179
+ def do_command(c)
180
+ return nil if !c
181
+ if @target.respond_to?(Array(c).first)
182
+ @lastcmd = c
183
+ @target.instance_eval { send(*Array(c)) }
184
+ else
185
+ @lastchar = "Unbound: #{Array(c).first.inspect}"
186
+ end
187
+ end
188
+
189
+ def read_input
190
+ c = get_command
191
+ if !c
192
+ Thread.pass
193
+ return
194
+ end
195
+ if Array(c).first == :insert_char
196
+ # FIXME: Attempt to combine multiple :insert_char into one.
197
+ #Probably should happen in get_command
198
+ #while (c2 = get_command) && Array(c2).first == :insert_char
199
+ # c.last << c2.last
200
+ #end
201
+ #@commands << c
202
+ #c = c2
203
+ #return nil if !c
204
+ end
205
+ @commands << c
206
+ Thread.pass
207
+ end
208
+
209
+ def next_command
210
+ if @commands.empty?
211
+ sleep(0.001)
212
+ Thread.pass
213
+ end
214
+ @commands.shift
215
+ end
216
+
217
+ def handle_input(prefix="",timeout=0.1)
218
+ if c = next_command
219
+ do_command(c)
220
+ end
221
+ return c
222
+ end
223
+
224
+ def pry(e=nil)
225
+ pause do
226
+ cleanup
227
+ puts ANSI.cls
228
+ binding.pry
229
+ end
230
+ setup
231
+ end
232
+
233
+ def suspend
234
+ pause do
235
+ yield if block_given?
236
+ Process.kill("STOP", 0)
237
+ end
238
+ end
239
+
240
+ def resume
241
+ @mode = :cooked
242
+ setup
243
+ end
244
+ end
245
+ end
246
+
247
+ at_exit do
248
+ #
249
+ # FIXME: This is a workaround for Controller putting
250
+ # STDIN into nonblocking mode and not cleaning up, which
251
+ # causes all kind of problems with a variety of tools (more,
252
+ # docker etc.) which expect it to be blocking.
253
+ Termcontroller::Controller.pause! do
254
+ stdin_flags = STDIN.fcntl(Fcntl::F_GETFL)
255
+ STDIN.fcntl(Fcntl::F_SETFL, stdin_flags & ~Fcntl::O_NONBLOCK) #
256
+ IO.console.cooked!
257
+ end
258
+ end
@@ -1,3 +1,3 @@
1
1
  module Termcontroller
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require "termcontroller/version"
2
+ require "termcontroller/controller"
2
3
 
3
4
  module Termcontroller
4
5
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: termcontroller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
@@ -27,6 +27,7 @@ files:
27
27
  - bin/console
28
28
  - bin/setup
29
29
  - lib/termcontroller.rb
30
+ - lib/termcontroller/controller.rb
30
31
  - lib/termcontroller/version.rb
31
32
  - termcontroller.gemspec
32
33
  homepage: https://github.com/vidarh/termcontroller