ruco 0.0.20 → 0.0.21
Sign up to get free protection for your applications and to get access to all the features.
- 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
|