ruco 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ group :dev do # not development <-> would add unneeded development dependencies in gemspec
4
+ gem 'rake'
5
+ gem 'rspec', '~>2'
6
+ gem 'jeweler'
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.8.7)
11
+ rspec (2.0.1)
12
+ rspec-core (~> 2.0.1)
13
+ rspec-expectations (~> 2.0.1)
14
+ rspec-mocks (~> 2.0.1)
15
+ rspec-core (2.0.1)
16
+ rspec-expectations (2.0.1)
17
+ diff-lcs (>= 1.1.2)
18
+ rspec-mocks (2.0.1)
19
+ rspec-core (~> 2.0.1)
20
+ rspec-expectations (~> 2.0.1)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ jeweler
27
+ rake
28
+ rspec (~> 2)
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ task :default => :spec
2
+ require "rspec/core/rake_task"
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.rspec_opts = '--backtrace --color'
5
+ end
6
+
7
+ task :run do
8
+ file = 'spec/temp.txt'
9
+ File.open(file, 'w'){|f|f.write("12345\n1234\n#{'abcdefg'*20}\n123")}
10
+ exec "./bin/ruco #{file}"
11
+ end
12
+
13
+ task :try do
14
+ require 'curses'
15
+ Curses.setpos(0,0)
16
+ Curses.addstr("xxxxxxxx\nyyyyyyy");
17
+ Curses.getch
18
+ end
19
+
20
+ task :key do
21
+ require 'curses'
22
+
23
+ Curses.noecho
24
+ Curses.raw
25
+ Curses.stdscr.keypad(true)
26
+
27
+ count = 0
28
+ loop do
29
+ count = (count + 1) % 20
30
+ key = Curses.getch
31
+ break if key == ?\C-c
32
+ Curses.setpos(count,0)
33
+ Curses.addstr("#{key.inspect} ");
34
+ end
35
+ end
36
+
37
+ begin
38
+ require 'jeweler'
39
+ Jeweler::Tasks.new do |gem|
40
+ gem.name = 'ruco'
41
+ gem.summary = "Commandline editor written in ruby"
42
+ gem.email = "michael@grosser.it"
43
+ gem.homepage = "http://github.com/grosser/#{gem.name}"
44
+ gem.authors = ["Michael Grosser"]
45
+ end
46
+
47
+ Jeweler::GemcutterTasks.new
48
+ rescue LoadError
49
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
50
+ end
data/Readme.md ADDED
@@ -0,0 +1,28 @@
1
+ Commandline editor written in ruby
2
+
3
+ Alpha, do not use...
4
+
5
+ Finished:
6
+ - viewing / scrolling / editing / saving / creating
7
+
8
+ Install
9
+ =======
10
+ sudo gem install ruco
11
+
12
+ Usage
13
+ =====
14
+ ruco file.rb
15
+
16
+ TODO
17
+ =====
18
+ - delete key !?
19
+ - warnings / messages
20
+ - help
21
+ - syntax highlighting
22
+
23
+
24
+ Author
25
+ ======
26
+ [Michael Grosser](http://grosser.it)
27
+ grosser.michael@gmail.com
28
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/ruco ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ require 'curses'
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+ require 'ruco'
6
+
7
+ def write(line,row,text)
8
+ Curses.setpos(line,row)
9
+ Curses.addstr(text);
10
+ end
11
+
12
+ def init_screen
13
+ Curses.noecho # do not show typed chars
14
+ Curses.init_screen
15
+ Curses.stdscr.keypad(true) # enable arrow keys
16
+ Curses.raw # give us all other keys
17
+
18
+ begin
19
+ yield
20
+ ensure
21
+ Curses.close_screen
22
+ end
23
+ end
24
+
25
+ editor = Ruco::Editor.new(ARGV[0], :lines => Curses.stdscr.maxy, :columns => Curses.stdscr.maxx)
26
+
27
+ init_screen do
28
+ loop do
29
+ lines = editor.view.split("\n")
30
+ Curses.stdscr.maxy.times do |i|
31
+ line = lines[i] || ''
32
+ clearer = Curses.stdscr.maxx - line.size
33
+ clearer = " " * clearer
34
+ write(i, 0, line + clearer)
35
+ end
36
+
37
+ Curses.setpos(editor.cursor_line, editor.cursor_column)
38
+
39
+ key = Curses.getch
40
+
41
+ case key
42
+ when Curses::Key::UP then editor.move(-1,0)
43
+ when Curses::Key::DOWN then editor.move(1,0)
44
+ when Curses::Key::RIGHT then editor.move(0,1)
45
+ when Curses::Key::LEFT then editor.move(0,-1)
46
+ when 32..126 then editor.insert(key.chr) # printable
47
+ when 10 then editor.insert("\n") # enter
48
+ when 263 then editor.delete(-1) # backspace
49
+ when ?\C-w, ?\C-q then break # quit
50
+ end
51
+ end
52
+ end
data/lib/ruco.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'ruco/editor'
2
+ require 'ruco/core_ext/string'
3
+ require 'ruco/core_ext/array'
4
+
5
+ module Ruco
6
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
7
+ end
@@ -0,0 +1,10 @@
1
+ # http://snippets.dzone.com/posts/show/5119
2
+ class Array
3
+ def map_with_index!
4
+ each_with_index do |e, idx| self[idx] = yield(e, idx); end
5
+ end
6
+
7
+ def map_with_index(&block)
8
+ dup.map_with_index!(&block)
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ class String
2
+ def naive_split(pattern)
3
+ string = self.dup
4
+ found = []
5
+
6
+ while position = string.index(pattern)
7
+ found << string.slice!(0, position)
8
+ string.slice!(0,[pattern.size,1].max)
9
+ end
10
+
11
+ found << string
12
+ found
13
+ end
14
+ end
@@ -0,0 +1,144 @@
1
+ module Ruco
2
+ class Editor
3
+ SCROLLING_OFFSET = 20
4
+
5
+ attr_reader :cursor_line, :cursor_column
6
+
7
+ def initialize(file, options)
8
+ @file = file
9
+ @options = options
10
+ @content = (File.exist?(@file) ? File.read(@file) : '')
11
+ @line = 0
12
+ @column = 0
13
+ @cursor_line = 0
14
+ @cursor_column = 0
15
+ @scrolled_lines = 0
16
+ @scrolled_columns = 0
17
+ @options[:line_scrolling_offset] ||= @options[:lines] / 2
18
+ @options[:column_scrolling_offset] ||= @options[:columns] / 2
19
+ end
20
+
21
+ def view
22
+ Array.new(@options[:lines]).map_with_index do |_,i|
23
+ (lines[i + @scrolled_lines] || "").slice(@scrolled_columns, @options[:columns])
24
+ end * "\n" + "\n"
25
+ end
26
+
27
+ def move(line, column)
28
+ @line = [[@line + line, 0].max, lines.size].min
29
+ @column = [[@column + column, 0].max, (lines[@line]||'').size].min
30
+
31
+ adjust_view
32
+ end
33
+
34
+ def insert(text)
35
+ insert_into_content cursor_index, text
36
+ move_according_to_insert(text)
37
+ end
38
+
39
+ def delete(count)
40
+ if count > 0
41
+ @content.slice!(cursor_index, count)
42
+ else
43
+ backspace(count.abs)
44
+ end
45
+ end
46
+
47
+ def backspace(count)
48
+ start_index = cursor_index - count
49
+ if start_index < 0
50
+ count += start_index
51
+ start_index = 0
52
+ end
53
+
54
+ @content.slice!(start_index, count)
55
+ set_cursor_to_index start_index
56
+ end
57
+
58
+ def save
59
+ File.open(@file,'w'){|f| f.write(@content) }
60
+ end
61
+
62
+ def cursor
63
+ [cursor_line, cursor_column]
64
+ end
65
+
66
+ private
67
+
68
+ def lines
69
+ @content.naive_split("\n")
70
+ end
71
+
72
+ def adjust_view
73
+ reposition_cursor
74
+ scroll_column_into_view
75
+ scroll_line_into_view
76
+ reposition_cursor
77
+ end
78
+
79
+ def scroll_column_into_view
80
+ offset = [@options[:column_scrolling_offset], @options[:columns]].min
81
+
82
+ if @cursor_column >= @options[:columns]
83
+ @scrolled_columns = @column - @options[:columns] + offset
84
+ end
85
+
86
+ if @cursor_column < 0
87
+ @scrolled_columns = @column - offset
88
+ end
89
+
90
+ @scrolled_columns = [[@scrolled_columns, 0].max, @column].min
91
+ end
92
+
93
+ def scroll_line_into_view
94
+ offset = [@options[:line_scrolling_offset], @options[:lines]].min
95
+
96
+ if @cursor_line >= @options[:lines]
97
+ @scrolled_lines = @line - @options[:lines] + offset
98
+ end
99
+
100
+ if @cursor_line < 0
101
+ @scrolled_lines = @line - offset
102
+ end
103
+
104
+ @scrolled_lines = [[@scrolled_lines, 0].max, @line].min
105
+ end
106
+
107
+ def reposition_cursor
108
+ @cursor_column = @column - @scrolled_columns
109
+ @cursor_line = @line - @scrolled_lines
110
+ end
111
+
112
+ def insert_into_content(index, text)
113
+ # expand with newlines when inserting after maximum position
114
+ if index > @content.size
115
+ @content << "\n" * (index - @content.size)
116
+ end
117
+ @content.insert(index, text)
118
+ end
119
+
120
+ def cursor_index
121
+ insertion_point = lines[0...@line].join("\n").size + @column
122
+ insertion_point += 1 if @line > 0 # account for missing newline
123
+ insertion_point
124
+ end
125
+
126
+ def set_cursor_to_index(index)
127
+ jump = @content.slice(0, index).to_s.naive_split("\n")
128
+ @line = jump.size - 1
129
+ @column = jump.last.size
130
+ reposition_cursor
131
+ end
132
+
133
+ def move_according_to_insert(text)
134
+ inserted_lines = text.naive_split("\n")
135
+ if inserted_lines.size > 1
136
+ # column position does not add up when hitting return
137
+ @column = inserted_lines.last.size
138
+ move(inserted_lines.size - 1, 0)
139
+ else
140
+ move(inserted_lines.size - 1, inserted_lines.last.size)
141
+ end
142
+ end
143
+ end
144
+ end
data/ruco.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ruco}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2011-01-08}
13
+ s.default_executable = %q{ruco}
14
+ s.email = %q{michael@grosser.it}
15
+ s.executables = ["ruco"]
16
+ s.files = [
17
+ "Gemfile",
18
+ "Gemfile.lock",
19
+ "Rakefile",
20
+ "Readme.md",
21
+ "VERSION",
22
+ "bin/ruco",
23
+ "lib/ruco.rb",
24
+ "lib/ruco/core_ext/array.rb",
25
+ "lib/ruco/core_ext/string.rb",
26
+ "lib/ruco/editor.rb",
27
+ "ruco.gemspec",
28
+ "spec/ruco/core_ext/string_spec.rb",
29
+ "spec/ruco_spec.rb",
30
+ "spec/spec_helper.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/grosser/ruco}
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.7}
35
+ s.summary = %q{Commandline editor written in ruby}
36
+ s.test_files = [
37
+ "spec/ruco/core_ext/string_spec.rb",
38
+ "spec/ruco_spec.rb",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ else
48
+ end
49
+ else
50
+ end
51
+ end
52
+
@@ -0,0 +1,17 @@
1
+ require File.expand_path('spec/spec_helper')
2
+
3
+ describe String do
4
+ describe :naive_split do
5
+ it "splits repeated pattern" do
6
+ "aaa".naive_split('a').should == ['','','','']
7
+ end
8
+
9
+ it "splits normal stuff" do
10
+ "abacad".naive_split('a').should == ['','b','c','d']
11
+ end
12
+
13
+ it "splits empty into 1" do
14
+ "".naive_split('a').should == ['']
15
+ end
16
+ end
17
+ end
data/spec/ruco_spec.rb ADDED
@@ -0,0 +1,240 @@
1
+ require File.expand_path('spec/spec_helper')
2
+
3
+ describe Ruco do
4
+ def write(content)
5
+ File.open(@file,'w'){|f| f.write(content) }
6
+ end
7
+
8
+ let(:editor){ Ruco::Editor.new(@file, :lines => 3, :columns => 5, :line_scrolling_offset => 5, :column_scrolling_offset => 5) }
9
+
10
+ it "has a VERSION" do
11
+ Ruco::VERSION.should =~ /^\d+\.\d+\.\d+$/
12
+ end
13
+
14
+ before do
15
+ @file = 'spec/temp.txt'
16
+ end
17
+
18
+ describe 'moving' do
19
+ before do
20
+ write(" \n \n ")
21
+ end
22
+
23
+ it "starts at 0,0" do
24
+ editor.cursor.should == [0,0]
25
+ end
26
+
27
+ it "can move" do
28
+ editor.move(1,2)
29
+ editor.cursor.should == [1,2]
30
+ editor.move(1,1)
31
+ editor.cursor.should == [2,3]
32
+ end
33
+
34
+ it "can move in empty file" do
35
+ write("\n\n\n")
36
+ editor.move(2,0)
37
+ editor.cursor.should == [2,0]
38
+ end
39
+
40
+ it "cannot move left/top off screen" do
41
+ editor.move(-1,-1)
42
+ editor.cursor.should == [0,0]
43
+ end
44
+
45
+ it "cannot move right of characters" do
46
+ editor.move(2,6)
47
+ editor.cursor.should == [2,4]
48
+ end
49
+
50
+ it "gets reset to empty line when moving past lines" do
51
+ write(" ")
52
+ editor.move(6,3)
53
+ editor.cursor.should == [1,0]
54
+ end
55
+
56
+ describe 'column scrolling' do
57
+ it "can scroll columns" do
58
+ write("123456789\n123")
59
+ editor.move(0,4)
60
+ editor.view.should == "12345\n123\n\n"
61
+ editor.cursor_column.should == 4
62
+
63
+ editor.move(0,1)
64
+ editor.view.should == "6789\n\n\n"
65
+ editor.cursor_column.should == 0
66
+ end
67
+
68
+ it "cannot scroll past the screen" do
69
+ write('123456789')
70
+ editor.move(0,4)
71
+ 6.times{ editor.move(0,1) }
72
+ editor.view.should == "6789\n\n\n"
73
+ editor.cursor_column.should == 4
74
+ end
75
+
76
+ it "can scroll columns backwards" do
77
+ write('123456789')
78
+ editor.move(0,5)
79
+ editor.view.should == "6789\n\n\n"
80
+
81
+ editor.move(0,-1)
82
+ editor.view.should == "12345\n\n\n"
83
+ editor.cursor_column.should == 4
84
+ end
85
+ end
86
+
87
+ describe 'line scrolling' do
88
+ before do
89
+ write("1\n2\n3\n4\n5\n6\n7\n8\n9")
90
+ end
91
+
92
+ it "can scroll lines down (at maximum of screen size)" do
93
+ editor.move(2,0)
94
+ editor.view.should == "1\n2\n3\n"
95
+
96
+ editor.move(1,0)
97
+ editor.view.should == "4\n5\n6\n"
98
+ editor.cursor_line.should == 0
99
+ end
100
+
101
+ it "can scroll till end of file" do
102
+ editor.move(15,0)
103
+ editor.view.should == "\n\n\n"
104
+ editor.cursor_line.should == 0
105
+ end
106
+ end
107
+ end
108
+
109
+ describe 'viewing' do
110
+ before do
111
+ write('')
112
+ end
113
+
114
+ it "displays an empty screen" do
115
+ editor.view.should == "\n\n\n"
116
+ end
117
+
118
+ it "displays short file content" do
119
+ write('xxx')
120
+ editor.view.should == "xxx\n\n\n"
121
+ end
122
+
123
+ it "displays long file content" do
124
+ write('1234567')
125
+ editor.view.should == "12345\n\n\n"
126
+ end
127
+
128
+ it "displays multiline-file content" do
129
+ write("xxx\nyyy\nzzz\niii")
130
+ editor.view.should == "xxx\nyyy\nzzz\n"
131
+ end
132
+ end
133
+
134
+ describe 'inserting' do
135
+ before do
136
+ write('')
137
+ end
138
+
139
+ it "can insert new chars" do
140
+ write('123')
141
+ editor.move(0,1)
142
+ editor.insert('ab')
143
+ editor.view.should == "1ab23\n\n\n"
144
+ editor.cursor.should == [0,3]
145
+ end
146
+
147
+ it "can insert new newlines" do
148
+ editor.insert("ab\nc")
149
+ editor.view.should == "ab\nc\n\n"
150
+ editor.cursor.should == [1,1]
151
+ end
152
+
153
+ it "jumps to correct column when inserting newline" do
154
+ write("abc\ndefg")
155
+ editor.move(1,2)
156
+ editor.insert("1\n23")
157
+ editor.view.should == "abc\nde1\n23fg\n"
158
+ editor.cursor.should == [2,2]
159
+ end
160
+
161
+ it "jumps to correct column when inserting 1 newline" do
162
+ write("abc\ndefg")
163
+ editor.move(1,2)
164
+ editor.insert("\n")
165
+ editor.view.should == "abc\nde\nfg\n"
166
+ editor.cursor.should == [2,0]
167
+ end
168
+
169
+ it "can add newlines to the end" do
170
+ write('')
171
+ editor.insert("\n")
172
+ editor.insert("\n")
173
+ editor.cursor.should == [2,0]
174
+ end
175
+
176
+ it "can add newlines to the moveable end" do
177
+ write('')
178
+ editor.move(1,0)
179
+ editor.insert("\n")
180
+ editor.cursor.should == [2,0]
181
+ end
182
+ end
183
+
184
+ describe 'save' do
185
+ it 'stores the file' do
186
+ write('xxx')
187
+ editor.insert('a')
188
+ editor.save
189
+ File.read(@file).should == 'axxx'
190
+ end
191
+
192
+ it 'creates the file' do
193
+ `rm #{@file}`
194
+ editor.insert('a')
195
+ editor.save
196
+ File.read(@file).should == 'a'
197
+ end
198
+ end
199
+
200
+ describe 'delete' do
201
+ it 'removes a char' do
202
+ write('123')
203
+ editor.delete(1)
204
+ editor.view.should == "23\n\n\n"
205
+ editor.cursor.should == [0,0]
206
+ end
207
+
208
+ it 'removes a line' do
209
+ write("123\n45")
210
+ editor.move(0,3)
211
+ editor.delete(1)
212
+ editor.view.should == "12345\n\n\n"
213
+ editor.cursor.should == [0,3]
214
+ end
215
+
216
+ it "cannot backspace over 0,0" do
217
+ write("aa")
218
+ editor.move(0,1)
219
+ editor.delete(-3)
220
+ editor.view.should == "a\n\n\n"
221
+ editor.cursor.should == [0,0]
222
+ end
223
+
224
+ it 'backspaces a char' do
225
+ write('123')
226
+ editor.move(0,3)
227
+ editor.delete(-1)
228
+ editor.view.should == "12\n\n\n"
229
+ editor.cursor.should == [0,2]
230
+ end
231
+
232
+ it 'backspaces a newline' do
233
+ write("1\n234")
234
+ editor.move(1,0)
235
+ editor.delete(-1)
236
+ editor.view.should == "1234\n\n\n"
237
+ editor.cursor.should == [0,1]
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'ruco'
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruco
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Michael Grosser
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-08 00:00:00 +01:00
19
+ default_executable: ruco
20
+ dependencies: []
21
+
22
+ description:
23
+ email: michael@grosser.it
24
+ executables:
25
+ - ruco
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - Gemfile
32
+ - Gemfile.lock
33
+ - Rakefile
34
+ - Readme.md
35
+ - VERSION
36
+ - bin/ruco
37
+ - lib/ruco.rb
38
+ - lib/ruco/core_ext/array.rb
39
+ - lib/ruco/core_ext/string.rb
40
+ - lib/ruco/editor.rb
41
+ - ruco.gemspec
42
+ - spec/ruco/core_ext/string_spec.rb
43
+ - spec/ruco_spec.rb
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/grosser/ruco
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.7
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Commandline editor written in ruby
79
+ test_files:
80
+ - spec/ruco/core_ext/string_spec.rb
81
+ - spec/ruco_spec.rb
82
+ - spec/spec_helper.rb