ruco 0.0.20 → 0.0.21
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.
- data/Readme.md +1 -1
- data/VERSION +1 -1
- data/bin/ruco +11 -3
- data/lib/ruco.rb +1 -0
- data/lib/ruco/application.rb +13 -4
- data/lib/ruco/editor.rb +2 -3
- data/lib/ruco/keyboard.rb +106 -50
- data/lib/ruco/text_area.rb +5 -0
- data/ruco.gemspec +4 -2
- data/spec/ruco/application_spec.rb +6 -0
- data/spec/ruco/keyboard_spec.rb +78 -0
- data/spec/spec_helper.rb +1 -0
- metadata +6 -4
data/Readme.md
CHANGED
@@ -61,7 +61,7 @@ TIPS
|
|
61
61
|
TODO
|
62
62
|
=====
|
63
63
|
- session storage (stay at same line/column when reopening)
|
64
|
-
-
|
64
|
+
- paste detection ? (ctrl+shift+insert / Cmd+v) -> no indentation
|
65
65
|
- selecting -> delete / overwrite / copy / cut
|
66
66
|
- smart staying at end of line/column when changing line
|
67
67
|
- warnings / messages
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.21
|
data/bin/ruco
CHANGED
@@ -95,16 +95,24 @@ end
|
|
95
95
|
@options = parse_options
|
96
96
|
|
97
97
|
require 'ruco'
|
98
|
-
require 'ruco/keyboard' # needs curses <-> not loaded for specs
|
99
98
|
|
100
99
|
app = Ruco::Application.new(ARGV[0], :lines => Curses.stdscr.maxy, :columns => Curses.stdscr.maxx)
|
101
100
|
|
102
101
|
init_screen do
|
103
102
|
show_app app
|
104
103
|
|
105
|
-
Keyboard.
|
104
|
+
Keyboard.input do
|
105
|
+
Curses.getch
|
106
|
+
end
|
107
|
+
|
108
|
+
Keyboard.output do |key|
|
106
109
|
debug_key(key) if @options[:debug_keys]
|
107
|
-
|
110
|
+
if key == :resize
|
111
|
+
app.resize(Curses.stdscr.maxy, Curses.stdscr.maxx)
|
112
|
+
@screen.clear # clear cache
|
113
|
+
else
|
114
|
+
result = app.key key
|
115
|
+
end
|
108
116
|
break if result == :quit
|
109
117
|
show_app app
|
110
118
|
end
|
data/lib/ruco.rb
CHANGED
data/lib/ruco/application.rb
CHANGED
@@ -77,6 +77,13 @@ module Ruco
|
|
77
77
|
instance_exec(&block)
|
78
78
|
end
|
79
79
|
|
80
|
+
def resize(lines, columns)
|
81
|
+
@options[:lines] = lines
|
82
|
+
@options[:columns] = columns
|
83
|
+
create_components
|
84
|
+
@editor.resize(editor_lines, columns)
|
85
|
+
end
|
86
|
+
|
80
87
|
private
|
81
88
|
|
82
89
|
attr_reader :editor, :status, :command
|
@@ -134,14 +141,16 @@ module Ruco
|
|
134
141
|
|
135
142
|
def create_components
|
136
143
|
@status_lines = 1
|
137
|
-
|
138
|
-
editor_lines = @options[:lines] - @status_lines - command_lines
|
139
|
-
|
140
|
-
@editor = Ruco::Editor.new(@file, :lines => editor_lines, :columns => @options[:columns])
|
144
|
+
@editor ||= Ruco::Editor.new(@file, :lines => editor_lines, :columns => @options[:columns])
|
141
145
|
@status = Ruco::StatusBar.new(@editor, :columns => @options[:columns])
|
142
146
|
@command = Ruco::CommandBar.new(:columns => @options[:columns])
|
143
147
|
command.cursor_line = editor_lines
|
144
148
|
@focused = @editor
|
145
149
|
end
|
150
|
+
|
151
|
+
def editor_lines
|
152
|
+
command_lines = 1
|
153
|
+
editor_lines = @options[:lines] - @status_lines - command_lines
|
154
|
+
end
|
146
155
|
end
|
147
156
|
end
|
data/lib/ruco/editor.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Ruco
|
2
2
|
class Editor
|
3
3
|
attr_reader :file
|
4
|
-
delegate :view, :move, :cursor, :delete_line, :to => :text_area
|
4
|
+
delegate :view, :move, :cursor, :resize, :delete_line, :to => :text_area
|
5
5
|
|
6
6
|
def initialize(file, options)
|
7
7
|
@file = file
|
8
|
-
@options = options
|
9
8
|
content = (File.exist?(@file) ? File.read(@file) : '')
|
10
|
-
@text_area = TextArea.new(content,
|
9
|
+
@text_area = TextArea.new(content, options)
|
11
10
|
@modified = false
|
12
11
|
end
|
13
12
|
|
data/lib/ruco/keyboard.rb
CHANGED
@@ -1,71 +1,127 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
1
3
|
class Keyboard
|
4
|
+
ENTER = 13
|
5
|
+
IS_18 = RUBY_VERSION =~ /^1\.8/
|
2
6
|
SEQUENCE_TIMEOUT = 0.01
|
3
7
|
NOTHING = 4294967295 # getch returns this as 'nothing' on 1.8 but nil on 1.9.2
|
4
8
|
A_TO_Z = ('a'..'z').to_a
|
5
9
|
|
6
|
-
def self.
|
7
|
-
|
8
|
-
|
10
|
+
def self.input(&block)
|
11
|
+
@input = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.output
|
15
|
+
@sequence = nil
|
16
|
+
@started = Time.now.to_f
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
loop do
|
19
|
+
key = fetch_user_input
|
20
|
+
if sequence_finished?
|
21
|
+
if needs_paste_fix?(@sequence)
|
22
|
+
yield bytes_to_string(@sequence)
|
14
23
|
else
|
15
|
-
@sequence
|
24
|
+
bytes_to_key_codes(@sequence).each{|c| yield c }
|
16
25
|
end
|
17
|
-
|
26
|
+
@sequence = nil
|
18
27
|
end
|
19
|
-
|
20
28
|
next if key == NOTHING
|
29
|
+
start_or_append_sequence key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.translate_key_to_code(key)
|
36
|
+
case key
|
37
|
+
|
38
|
+
# move
|
39
|
+
when Curses::Key::UP then :up
|
40
|
+
when Curses::Key::DOWN then :down
|
41
|
+
when Curses::Key::RIGHT then :right
|
42
|
+
when Curses::Key::LEFT then :left
|
43
|
+
when 554 then :"Ctrl+right"
|
44
|
+
when 555 then :"Ctrl+Shift+right"
|
45
|
+
when 539 then :"Ctrl+left"
|
46
|
+
when 540 then :"Ctrl+Shift+left"
|
47
|
+
when 560 then :"Ctrl+up"
|
48
|
+
when 519 then :"Ctrl+down"
|
49
|
+
when Curses::KEY_END then :end
|
50
|
+
when Curses::KEY_HOME then :home
|
51
|
+
when Curses::KEY_NPAGE then :page_down
|
52
|
+
when Curses::KEY_PPAGE then :page_up
|
53
|
+
when Curses::KEY_IC then :insert
|
54
|
+
when Curses::KEY_F0..Curses::KEY_F63 then :"F#{key - Curses::KEY_F0}"
|
55
|
+
|
56
|
+
# modify
|
57
|
+
when 9 then :tab
|
58
|
+
when ENTER then :enter # shadows Ctrl+m
|
59
|
+
when 263, 127 then :backspace # ubuntu / mac
|
60
|
+
when Curses::KEY_DC then :delete
|
61
|
+
|
62
|
+
# misc
|
63
|
+
when 0 then :"Ctrl+space"
|
64
|
+
when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}"
|
65
|
+
when 27 then :escape
|
66
|
+
when Curses::KEY_RESIZE then :resize
|
67
|
+
else
|
68
|
+
key.chr
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.fetch_user_input
|
73
|
+
key = @input.call || NOTHING
|
74
|
+
key = key.ord if key.is_a?(String) # ruby 1.9 fix
|
75
|
+
key
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.start_or_append_sequence(key)
|
79
|
+
@started = Time.now.to_f
|
80
|
+
@sequence ||= []
|
81
|
+
@sequence << key
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.bytes_to_string(bytes)
|
85
|
+
bytes.pack('c*').gsub("\r","\n").force_encoding('utf-8')
|
86
|
+
end
|
87
|
+
|
88
|
+
# split a text so fast-typers do not get bugs like ^B^C in output
|
89
|
+
def self.bytes_to_key_codes(bytes)
|
90
|
+
result = []
|
91
|
+
multi_byte = nil
|
21
92
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# move
|
27
|
-
when Curses::Key::UP then :up
|
28
|
-
when Curses::Key::DOWN then :down
|
29
|
-
when Curses::Key::RIGHT then :right
|
30
|
-
when Curses::Key::LEFT then :left
|
31
|
-
when 554 then :"Ctrl+right"
|
32
|
-
when 555 then :"Ctrl+Shift+right"
|
33
|
-
when 539 then :"Ctrl+left"
|
34
|
-
when 540 then :"Ctrl+Shift+left"
|
35
|
-
when 560 then :"Ctrl+up"
|
36
|
-
when 519 then :"Ctrl+down"
|
37
|
-
when Curses::KEY_END then :end
|
38
|
-
when Curses::KEY_HOME then :home
|
39
|
-
when Curses::KEY_NPAGE then :page_down
|
40
|
-
when Curses::KEY_PPAGE then :page_up
|
41
|
-
when Curses::KEY_IC then :insert
|
42
|
-
when Curses::KEY_F0..Curses::KEY_F63 then :"F#{key - Curses::KEY_F0}"
|
43
|
-
|
44
|
-
# modify
|
45
|
-
when 9 then :tab
|
46
|
-
when 13 then :enter # shadows Ctrl+m
|
47
|
-
when 263, 127 then :backspace # ubuntu / mac
|
48
|
-
when Curses::KEY_DC then :delete
|
49
|
-
|
50
|
-
# misc
|
51
|
-
when 0 then :"Ctrl+space"
|
52
|
-
when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}"
|
53
|
-
when 27 then :escape
|
54
|
-
when 195..197 # start of unicode sequence
|
55
|
-
@sequence = [key]
|
56
|
-
@sequence_started = Time.now.to_f
|
57
|
-
next
|
93
|
+
bytes.each do |byte|
|
94
|
+
if multi_byte_part?(byte)
|
95
|
+
multi_byte ||= []
|
96
|
+
multi_byte << byte
|
58
97
|
else
|
59
|
-
|
98
|
+
if multi_byte
|
99
|
+
# finish multi-byte char
|
100
|
+
result << bytes_to_string(multi_byte)
|
101
|
+
multi_byte = nil
|
102
|
+
end
|
103
|
+
result << translate_key_to_code(byte)
|
60
104
|
end
|
105
|
+
end
|
61
106
|
|
62
|
-
|
107
|
+
if multi_byte
|
108
|
+
result << bytes_to_string(multi_byte)
|
63
109
|
end
|
110
|
+
|
111
|
+
result
|
64
112
|
end
|
65
113
|
|
66
|
-
|
114
|
+
# not ascii and not control-char
|
115
|
+
def self.multi_byte_part?(byte)
|
116
|
+
127 < byte and byte < 256
|
117
|
+
end
|
67
118
|
|
68
119
|
def self.sequence_finished?
|
69
|
-
(Time.now.to_f - @
|
120
|
+
@sequence and (Time.now.to_f - @started) > SEQUENCE_TIMEOUT
|
121
|
+
end
|
122
|
+
|
123
|
+
# paste of multiple \n or \n in text would cause weird indentation
|
124
|
+
def self.needs_paste_fix?(sequence)
|
125
|
+
sequence.size > 1 and sequence.include?(ENTER)
|
70
126
|
end
|
71
127
|
end
|
data/lib/ruco/text_area.rb
CHANGED
data/ruco.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruco}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.21"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Michael Grosser"]
|
12
|
-
s.date = %q{2011-01-
|
12
|
+
s.date = %q{2011-01-21}
|
13
13
|
s.default_executable = %q{ruco}
|
14
14
|
s.email = %q{michael@grosser.it}
|
15
15
|
s.executables = ["ruco"]
|
@@ -40,6 +40,7 @@ Gem::Specification.new do |s|
|
|
40
40
|
"spec/ruco/core_ext/string_spec.rb",
|
41
41
|
"spec/ruco/editor_spec.rb",
|
42
42
|
"spec/ruco/form_spec.rb",
|
43
|
+
"spec/ruco/keyboard_spec.rb",
|
43
44
|
"spec/ruco/status_bar_spec.rb",
|
44
45
|
"spec/ruco/text_area_spec.rb",
|
45
46
|
"spec/ruco_spec.rb",
|
@@ -55,6 +56,7 @@ Gem::Specification.new do |s|
|
|
55
56
|
"spec/ruco/core_ext/string_spec.rb",
|
56
57
|
"spec/ruco/editor_spec.rb",
|
57
58
|
"spec/ruco/form_spec.rb",
|
59
|
+
"spec/ruco/keyboard_spec.rb",
|
58
60
|
"spec/ruco/status_bar_spec.rb",
|
59
61
|
"spec/ruco/text_area_spec.rb",
|
60
62
|
"spec/ruco_spec.rb",
|
@@ -47,6 +47,12 @@ describe Ruco::Application do
|
|
47
47
|
app.cursor.should == [2,0] # 0 offset + 1 for statusbar
|
48
48
|
end
|
49
49
|
|
50
|
+
it "can resize" do
|
51
|
+
write("01234567\n1\n2\n3\n4\n5678910111213\n6\n7\n8")
|
52
|
+
app.resize(8, 7)
|
53
|
+
app.view.should == "#{status}0123456\n1\n2\n3\n4\n5678910\n#{command}"
|
54
|
+
end
|
55
|
+
|
50
56
|
describe 'closing' do
|
51
57
|
it "can quit" do
|
52
58
|
result = app.key(:"Ctrl+w")
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path('spec/spec_helper')
|
3
|
+
|
4
|
+
describe Keyboard do
|
5
|
+
def output
|
6
|
+
keys = []
|
7
|
+
Timeout.timeout(0.3) do
|
8
|
+
Keyboard.output do |key|
|
9
|
+
keys << key
|
10
|
+
end
|
11
|
+
end
|
12
|
+
keys
|
13
|
+
rescue Timeout::Error
|
14
|
+
keys
|
15
|
+
end
|
16
|
+
|
17
|
+
def type(chars)
|
18
|
+
Keyboard.input do
|
19
|
+
char = chars.shift
|
20
|
+
if char == :sleep_long
|
21
|
+
sleep 0.1
|
22
|
+
nil
|
23
|
+
else
|
24
|
+
char
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can listen to simple keys" do
|
30
|
+
type [32]
|
31
|
+
output.should == [' ']
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can listen to multiple keys" do
|
35
|
+
type [32, :sleep_long, 97]
|
36
|
+
output.should == [' ','a']
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can listen ctrl+x" do
|
40
|
+
type [26]
|
41
|
+
output.should == [:'Ctrl+z']
|
42
|
+
end
|
43
|
+
|
44
|
+
it "can listen to enter" do
|
45
|
+
type [13]
|
46
|
+
output.should == [:enter]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "does not listen to nil / NOTHING" do
|
50
|
+
type [nil, Keyboard::NOTHING, 13]
|
51
|
+
output.should == [:enter]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "can fetch uft8-chars" do
|
55
|
+
type [195, 164]
|
56
|
+
output.should == ['ä']
|
57
|
+
end
|
58
|
+
|
59
|
+
it "cannot fetch long sequences" do
|
60
|
+
type [195, :sleep_long, 164]
|
61
|
+
output.map{|s|s.bytes.to_a}.should == [[195], [164]]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "fetches pastes between normal key strokes" do
|
65
|
+
type [32, :sleep_long, 32, 13, 32, :sleep_long, 32]
|
66
|
+
output.should == [' '," \n ",' ']
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns pastes that do not need indentation fix as normal chars" do
|
70
|
+
type [32, :sleep_long, 32, 32, 32, :sleep_long, 32]
|
71
|
+
output.should == [' ',' ',' ',' ',' ']
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns control chars separately" do
|
75
|
+
type [260, 127, 127, 261, 260, 195, 164, 261, 260, 195, 164]
|
76
|
+
output.should == [:left, :backspace, :backspace, :right, :left, "ä", :right, :left, "ä"]
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 21
|
9
|
+
version: 0.0.21
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Michael Grosser
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-21 00:00:00 +01:00
|
18
18
|
default_executable: ruco
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -67,6 +67,7 @@ files:
|
|
67
67
|
- spec/ruco/core_ext/string_spec.rb
|
68
68
|
- spec/ruco/editor_spec.rb
|
69
69
|
- spec/ruco/form_spec.rb
|
70
|
+
- spec/ruco/keyboard_spec.rb
|
70
71
|
- spec/ruco/status_bar_spec.rb
|
71
72
|
- spec/ruco/text_area_spec.rb
|
72
73
|
- spec/ruco_spec.rb
|
@@ -85,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
86
|
requirements:
|
86
87
|
- - ">="
|
87
88
|
- !ruby/object:Gem::Version
|
88
|
-
hash:
|
89
|
+
hash: -755587743
|
89
90
|
segments:
|
90
91
|
- 0
|
91
92
|
version: "0"
|
@@ -110,6 +111,7 @@ test_files:
|
|
110
111
|
- spec/ruco/core_ext/string_spec.rb
|
111
112
|
- spec/ruco/editor_spec.rb
|
112
113
|
- spec/ruco/form_spec.rb
|
114
|
+
- spec/ruco/keyboard_spec.rb
|
113
115
|
- spec/ruco/status_bar_spec.rb
|
114
116
|
- spec/ruco/text_area_spec.rb
|
115
117
|
- spec/ruco_spec.rb
|