ruco 0.0.22 → 0.0.23

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 CHANGED
@@ -5,16 +5,17 @@ Alpha, lets see if this works...
5
5
  Finished:
6
6
 
7
7
  - viewing / scrolling / editing / saving / creating
8
+ - selecting via Shift+left/right/up/down
8
9
  - Home/End + Page up/down
9
10
  - basic Tab support (tab == 2 space)
10
11
  - change-indicator (*)
11
- - writeable indicator (!)
12
+ - writable indicator (!)
12
13
  - backspace / delete
13
14
  - find / go to line
14
15
  - delete line
15
16
  - configuration via `~/.ruco.rb`
16
17
  - keeps indentation
17
- - paste from clipboard (default: Ctrl+v)
18
+ - cut, copy and paste (defaults: Ctrl+x/c/v)
18
19
  - paste-detection (e.g. cmd+v) -> clean indentation
19
20
 
20
21
  Install
@@ -58,15 +59,14 @@ Customize
58
59
  TIPS
59
60
  ====
60
61
  - [Ruby1.9] Unicode support -> install libncursesw5-dev before installing ruby (does not work for 1.8)
62
+ - [ssh vs clipboard] access your desktops clipboard by installing `xauth` on the server and then using `ssh -X`
61
63
 
62
64
  TODO
63
65
  =====
64
66
  - session storage (stay at same line/column when reopening)
65
- - selecting -> delete / overwrite / copy / cut
66
67
  - smart staying at end of line/column when changing line
67
68
  - warnings / messages
68
69
  - syntax highlighting
69
- - support more key-combos/codes in keyboard.rb
70
70
  - raise when binding to a unsupported key
71
71
  - search & replace
72
72
  - 1.8: unicode support <-> already finished but usable due to Curses (see encoding branch)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.22
1
+ 0.0.23
data/bin/ruco CHANGED
@@ -53,31 +53,47 @@ def init_screen
53
53
  end
54
54
  end
55
55
 
56
- def display(lines, offset, color)
56
+ def display(lines, offset, color_mask)
57
57
  @screen ||= [] # current screen is used as cache
58
- Curses.attrset color
59
58
 
60
59
  lines.each_with_index do |content, line|
60
+ colors = color_mask[line] || []
61
+
62
+ # expand line with whitespace
61
63
  line += offset
62
- clearer = Curses.stdscr.maxx - content.size
64
+ clearer = Curses.stdscr.maxx + 1 - content.size
63
65
  clearer = " " * clearer
64
66
  content += clearer
65
67
 
66
- next if @screen[line] == content
67
- @screen[line] = content
68
+ # cache !?
69
+ next if @screen[line] == [content, colors]
70
+ @screen[line] = [content, colors]
71
+
72
+ # position at start of line
73
+ index = 0
74
+ Curses.setpos(line,0)
75
+ Curses.attrset(Curses::A_NORMAL)
76
+
77
+ # write colored string
78
+ colors.each do |start, color|
79
+ Curses.addstr(content[index...start])
80
+ Curses.attrset color
81
+ index = start
82
+ end
83
+ Curses.addstr(content[index...-1])
68
84
 
69
- write(line, 0, content)
70
85
  write(line, 0, (rand(899)+100).to_s) if @options[:debug_cache]
71
86
  end
72
87
  end
73
88
 
74
89
  def show_app(app)
75
90
  lines = app.view.naive_split("\n")
91
+ color_mask = app.color_mask
76
92
 
77
93
  # TODO move this logic into application
78
- display([lines.first], 0, Curses::A_REVERSE)
79
- display(lines[1..-2], 1, Curses::A_NORMAL)
80
- display([lines.last], Curses.stdscr.maxy - 1, Curses::A_REVERSE)
94
+ display([lines.first], 0, [color_mask.first])
95
+ display(lines[1..-2], 1, color_mask[1..-2])
96
+ display([lines.last], Curses.stdscr.maxy - 1, [color_mask.last])
81
97
 
82
98
  Curses.setpos(app.cursor.line, app.cursor.column)
83
99
  end
data/lib/ruco.rb CHANGED
@@ -2,6 +2,7 @@ require 'ruco/core_ext/object'
2
2
  require 'ruco/core_ext/string'
3
3
  require 'ruco/core_ext/array'
4
4
  require 'ruco/core_ext/hash'
5
+ require 'ruco/core_ext/range'
5
6
 
6
7
  require 'ruco/keyboard'
7
8
  require 'ruco/cursor'
@@ -14,6 +14,11 @@ module Ruco
14
14
  status.view + "\n" + editor.view + command.view
15
15
  end
16
16
 
17
+ def color_mask
18
+ reverse = [[[0,Curses::A_REVERSE]]]
19
+ reverse + editor.color_mask + reverse
20
+ end
21
+
17
22
  def cursor
18
23
  Cursor.new(@focused.cursor.line + @status_lines, @focused.cursor.column)
19
24
  end
@@ -31,15 +36,33 @@ module Ruco
31
36
  case key
32
37
 
33
38
  # move
34
- when :up then @focused.move(:relative, -1,0)
35
39
  when :down then @focused.move(:relative, 1,0)
36
40
  when :right then @focused.move(:relative, 0,1)
41
+ when :up then @focused.move(:relative, -1,0)
37
42
  when :left then @focused.move(:relative, 0,-1)
38
43
  when :end then @focused.move :to_eol
39
44
  when :home then @focused.move :to_bol
40
45
  when :page_up then @focused.move :page_up
41
46
  when :page_down then @focused.move :page_down
42
47
 
48
+ # select
49
+ when :"Shift+down" then
50
+ @focused.selecting do
51
+ move(:relative, 1, 0)
52
+ end
53
+ when :"Shift+right"
54
+ @focused.selecting do
55
+ move(:relative, 0, 1)
56
+ end
57
+ when :"Shift+up"
58
+ @focused.selecting do
59
+ move(:relative, -1, 0)
60
+ end
61
+ when :"Shift+left" then
62
+ @focused.selecting do
63
+ move(:relative, 0, -1)
64
+ end
65
+
43
66
  # modify
44
67
  when :tab then @focused.insert("\t")
45
68
  when :enter then
@@ -92,7 +115,16 @@ module Ruco
92
115
  @actions = {}
93
116
 
94
117
  action :paste do
95
- editor.insert(Clipboard.paste('clipboard'))
118
+ @focused.insert(Clipboard.paste)
119
+ end
120
+
121
+ action :copy do
122
+ Clipboard.copy(@focused.text_in_selection)
123
+ end
124
+
125
+ action :cut do
126
+ Clipboard.copy(@focused.text_in_selection)
127
+ @focused.delete(0)
96
128
  end
97
129
 
98
130
  action :save do
@@ -130,6 +162,8 @@ module Ruco
130
162
  bind :"Ctrl+g", :go_to_line
131
163
  bind :"Ctrl+f", :find
132
164
  bind :"Ctrl+d", :delete_line
165
+ bind :"Ctrl+x", :cut
166
+ bind :"Ctrl+c", :copy
133
167
  bind :"Ctrl+v", :paste
134
168
  end
135
169
 
@@ -7,4 +7,27 @@ class Array
7
7
  def map_with_index(&block)
8
8
  dup.map_with_index!(&block)
9
9
  end
10
+ end
11
+
12
+ # TODO move this to cursor <-> use cursor for calculations
13
+ class Array
14
+ def between?(a,b)
15
+ self.>= a and self.<= b
16
+ end
17
+
18
+ def <(other)
19
+ (self.<=>other) == -1
20
+ end
21
+
22
+ def <=(other)
23
+ self.<(other) or self.==other
24
+ end
25
+
26
+ def >(other)
27
+ (self.<=>other) == 1
28
+ end
29
+
30
+ def >=(other)
31
+ self.>(other) or self.==other
32
+ end
10
33
  end
@@ -0,0 +1,6 @@
1
+ class Range
2
+ # http://stackoverflow.com/questions/699448/ruby-how-do-you-check-whether-a-range-contains-a-subset-of-another-range
3
+ def overlap?(other)
4
+ (first <= other.last) and (other.first <= last)
5
+ end
6
+ end
data/lib/ruco/editor.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Ruco
2
2
  class Editor
3
3
  attr_reader :file
4
- delegate :view, :move, :cursor, :resize, :delete_line, :to => :text_area
4
+ delegate :view, :selection, :text_in_selection, :color_mask, :selecting, :move, :cursor, :resize, :delete_line, :to => :text_area
5
5
 
6
6
  def initialize(file, options)
7
7
  @file = file
data/lib/ruco/keyboard.rb CHANGED
@@ -42,11 +42,15 @@ class Keyboard
42
42
  when Curses::Key::RIGHT then :right
43
43
  when Curses::Key::LEFT then :left
44
44
  when 554 then :"Ctrl+right"
45
+ when 393 then :"Shift+left"
45
46
  when 555 then :"Ctrl+Shift+right"
46
47
  when 539 then :"Ctrl+left"
48
+ when 402 then :"Shift+right"
47
49
  when 540 then :"Ctrl+Shift+left"
48
50
  when 560 then :"Ctrl+up"
51
+ when 337 then :"Shift+up"
49
52
  when 519 then :"Ctrl+down"
53
+ when 336 then :"Shift+down"
50
54
  when Curses::KEY_END then :end
51
55
  when Curses::KEY_HOME then :home
52
56
  when Curses::KEY_NPAGE then :page_down
@@ -1,6 +1,6 @@
1
1
  module Ruco
2
2
  class TextArea
3
- attr_reader :lines
3
+ attr_reader :lines, :selection
4
4
 
5
5
  def initialize(content, options)
6
6
  @lines = tabs_to_spaces(content).naive_split("\n")
@@ -21,6 +21,24 @@ module Ruco
21
21
  end * "\n" + "\n"
22
22
  end
23
23
 
24
+ def color_mask
25
+ mask = Array.new(@options[:lines])
26
+ return mask unless @selection
27
+
28
+ mask.map_with_index do |_,line|
29
+ visible = visible_area(line)
30
+ next unless @selection.overlap?(visible)
31
+
32
+ first = [@selection.first, visible.first].max
33
+ last = [@selection.last, visible.last].min
34
+
35
+ [
36
+ [first[1]-@scrolled_columns,Curses::A_REVERSE],
37
+ [last[1]-@scrolled_columns, Curses::A_NORMAL]
38
+ ]
39
+ end
40
+ end
41
+
24
42
  def move(where, *args)
25
43
  case where
26
44
  when :relative then
@@ -43,10 +61,35 @@ module Ruco
43
61
  else
44
62
  raise "Unknown move type #{where} with #{args.inspect}"
45
63
  end
64
+ @selection = nil unless @selecting
46
65
  adjust_view
47
66
  end
48
67
 
68
+ def selecting(&block)
69
+ start = if @selection
70
+ (position == @selection.first ? @selection.last : @selection.first)
71
+ else
72
+ position
73
+ end
74
+
75
+ @selecting = true
76
+ instance_exec(&block)
77
+ @selecting = false
78
+
79
+ sorted = [start, position].sort
80
+ @selection = sorted[0]..sorted[1]
81
+ end
82
+
83
+ def text_in_selection
84
+ return '' unless @selection
85
+ start = index_for_position(*@selection.first)
86
+ finish = index_for_position(*@selection.last)
87
+ content.slice(start, finish-start)
88
+ end
89
+
49
90
  def insert(text)
91
+ delete_content_in_selection if @selection
92
+
50
93
  text = tabs_to_spaces(text)
51
94
  if text == "\n" and @column >= after_last_word
52
95
  current_whitespace = current_line.match(/^\s*/)[0]
@@ -58,6 +101,11 @@ module Ruco
58
101
  end
59
102
 
60
103
  def delete(count)
104
+ if @selection
105
+ delete_content_in_selection
106
+ return
107
+ end
108
+
61
109
  if count > 0
62
110
  if current_line[@column..-1].size >= count
63
111
  current_line.slice!(@column, count)
@@ -80,12 +128,16 @@ module Ruco
80
128
  Cursor.new @cursor_line, @cursor_column
81
129
  end
82
130
 
83
- def cursor_index
84
- index = lines[0...@line].join("\n").size + @column
85
- index += 1 if @line > 0 # account for missing newline
131
+ def cursor_index(line=@line, column=@column)
132
+ index = lines[0...line].join("\n").size + column
133
+ index += 1 if line > 0 # account for missing newline
86
134
  index
87
135
  end
88
136
 
137
+ def index_for_position(line, column)
138
+ cursor_index(line, column)
139
+ end
140
+
89
141
  def position_for_index(index)
90
142
  jump = content.slice(0, index).to_s.naive_split("\n")
91
143
  [jump.size - 1, jump.last.size]
@@ -231,5 +283,23 @@ module Ruco
231
283
  def tabs_to_spaces(text)
232
284
  text.gsub("\t",' ' * Ruco::TAB_SIZE)
233
285
  end
286
+
287
+ def delete_content_in_selection
288
+ with_lines_as_string do |content|
289
+ start = index_for_position(*@selection.first)
290
+ finish = index_for_position(*@selection.last)
291
+ content.slice!(start, finish-start)
292
+ move(:to, *@selection.first)
293
+ end
294
+ @selection = nil
295
+ end
296
+
297
+ def visible_area(line)
298
+ line += @scrolled_lines
299
+ start_of_line = [line, @scrolled_columns]
300
+ last_visible_column = @scrolled_columns + @options[:columns]
301
+ end_of_line = [line, last_visible_column]
302
+ start_of_line..end_of_line
303
+ end
234
304
  end
235
305
  end
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.22"
8
+ s.version = "0.0.23"
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-22}
12
+ s.date = %q{2011-01-23}
13
13
  s.default_executable = %q{ruco}
14
14
  s.email = %q{michael@grosser.it}
15
15
  s.executables = ["ruco"]
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  "lib/ruco/core_ext/array.rb",
27
27
  "lib/ruco/core_ext/hash.rb",
28
28
  "lib/ruco/core_ext/object.rb",
29
+ "lib/ruco/core_ext/range.rb",
29
30
  "lib/ruco/core_ext/string.rb",
30
31
  "lib/ruco/cursor.rb",
31
32
  "lib/ruco/editor.rb",
@@ -37,6 +38,7 @@ Gem::Specification.new do |s|
37
38
  "ruco.gemspec",
38
39
  "spec/ruco/application_spec.rb",
39
40
  "spec/ruco/command_bar_spec.rb",
41
+ "spec/ruco/core_ext/array_spec.rb",
40
42
  "spec/ruco/core_ext/string_spec.rb",
41
43
  "spec/ruco/editor_spec.rb",
42
44
  "spec/ruco/form_spec.rb",
@@ -53,6 +55,7 @@ Gem::Specification.new do |s|
53
55
  s.test_files = [
54
56
  "spec/ruco/application_spec.rb",
55
57
  "spec/ruco/command_bar_spec.rb",
58
+ "spec/ruco/core_ext/array_spec.rb",
56
59
  "spec/ruco/core_ext/string_spec.rb",
57
60
  "spec/ruco/editor_spec.rb",
58
61
  "spec/ruco/form_spec.rb",
@@ -0,0 +1,31 @@
1
+ require File.expand_path('spec/spec_helper')
2
+
3
+ describe Array do
4
+ it "is bigger" do
5
+ [1].should > [0]
6
+ [1].should_not > [1]
7
+ end
8
+
9
+ it "is smaller" do
10
+ [1].should < [2]
11
+ [1].should_not < [1]
12
+ end
13
+
14
+ it "is smaller or equal" do
15
+ [1].should <= [1]
16
+ [1].should_not <= [0]
17
+ end
18
+
19
+ it "is bigger or equal" do
20
+ [1].should >= [1]
21
+ [1].should_not >= [2]
22
+ end
23
+
24
+ it "is between" do
25
+ [1].between?([1],[1]).should == true
26
+ [1].between?([1],[2]).should == true
27
+ [1].between?([0],[1]).should == true
28
+ [1].between?([0],[0]).should == false
29
+ [1].between?([2],[2]).should == false
30
+ end
31
+ end
@@ -184,6 +184,194 @@ describe Ruco::Editor do
184
184
  end
185
185
  end
186
186
 
187
+ describe :selecting do
188
+ before do
189
+ write('012345678')
190
+ end
191
+
192
+ it "remembers the selection" do
193
+ editor.selecting do
194
+ move(:to, 0, 4)
195
+ end
196
+ editor.selection.should == ([0,0]..[0,4])
197
+ end
198
+
199
+ it "expands the selection" do
200
+ editor.selecting do
201
+ move(:to, 0, 4)
202
+ move(:to, 0, 6)
203
+ end
204
+ editor.selection.should == ([0,0]..[0,6])
205
+ end
206
+
207
+ it "expand an old selection" do
208
+ editor.selecting do
209
+ move(:to, 0, 4)
210
+ end
211
+ editor.selecting do
212
+ move(:relative, 0, 2)
213
+ end
214
+ editor.selection.should == ([0,0]..[0,6])
215
+ end
216
+
217
+ it "can select backwards" do
218
+ editor.move(:to, 0, 4)
219
+ editor.selecting do
220
+ move(:relative, 0, -2)
221
+ end
222
+ editor.selecting do
223
+ move(:relative, 0, -2)
224
+ end
225
+ editor.selection.should == ([0,0]..[0,4])
226
+ end
227
+
228
+ it "can select multiple lines" do
229
+ write("012\n345\n678")
230
+ editor.move(:to, 0, 2)
231
+ editor.selecting do
232
+ move(:relative, 1, 0)
233
+ end
234
+ editor.selecting do
235
+ move(:relative, 1, 0)
236
+ end
237
+ editor.selection.should == ([0,2]..[2,2])
238
+ end
239
+
240
+ it "clears the selection once I move" do
241
+ editor.selecting do
242
+ move(:to, 0, 4)
243
+ end
244
+ editor.move(:relative, 0, 2)
245
+ editor.selection.should == nil
246
+ end
247
+
248
+ it "replaces the selection with insert" do
249
+ editor.selecting do
250
+ move(:to, 0, 4)
251
+ end
252
+ editor.insert('X')
253
+ editor.selection.should == nil
254
+ editor.cursor.should == [0,1]
255
+ editor.move(:to, 0,0)
256
+ editor.view.should == "X4567\n\n\n"
257
+ end
258
+
259
+ it "replaces the multi-line-selection with insert" do
260
+ write("123\n456\n789")
261
+ editor.move(:to, 0,1)
262
+ editor.selecting do
263
+ move(:to, 1,2)
264
+ end
265
+ editor.insert('X')
266
+ editor.selection.should == nil
267
+ editor.cursor.should == [0,2]
268
+ editor.move(:to, 0,0)
269
+ editor.view.should == "1X6\n789\n\n"
270
+ end
271
+
272
+ it "deletes selection delete" do
273
+ write("123\n456\n789")
274
+ editor.move(:to, 0,1)
275
+ editor.selecting do
276
+ move(:to, 1,2)
277
+ end
278
+ editor.delete(1)
279
+ editor.cursor.should == [0,1]
280
+ editor.move(:to, 0,0)
281
+ editor.view.should == "16\n789\n\n"
282
+ end
283
+ end
284
+
285
+ describe :text_in_selection do
286
+ before do
287
+ write("123\n456\n789")
288
+ end
289
+
290
+ it "returns '' if nothing is selected" do
291
+ editor.selecting do
292
+ move(:to, 1,1)
293
+ end
294
+ editor.text_in_selection.should == "123\n4"
295
+ end
296
+
297
+ it "returns selected text" do
298
+ editor.text_in_selection.should == ''
299
+ end
300
+ end
301
+
302
+ describe :color_mask do
303
+ it "is empty by default" do
304
+ editor.color_mask.should == [nil,nil,nil]
305
+ end
306
+
307
+ it "shows one-line selection" do
308
+ write('12345678')
309
+ editor.selecting do
310
+ move(:to, 0, 4)
311
+ end
312
+ editor.color_mask.should == [
313
+ [[0,262144],[4,0]],
314
+ nil,
315
+ nil,
316
+ ]
317
+ end
318
+
319
+ it "shows multi-line selection" do
320
+ write("012\n345\n678")
321
+ editor.move(:to, 0,1)
322
+ editor.selecting do
323
+ move(:to, 1, 1)
324
+ end
325
+ editor.color_mask.should == [
326
+ [[1,262144],[5,0]],
327
+ [[0,262144],[1,0]],
328
+ nil,
329
+ ]
330
+ end
331
+
332
+ it "shows the selection from offset" do
333
+ write('12345678')
334
+ editor.move(:to, 0, 2)
335
+ editor.selecting do
336
+ move(:to, 0, 4)
337
+ end
338
+ editor.color_mask.should == [
339
+ [[2,262144], [4,0]],
340
+ nil,
341
+ nil,
342
+ ]
343
+ end
344
+
345
+ it "shows the selection in nth line" do
346
+ write("\n12345678")
347
+ editor.move(:to, 1, 2)
348
+ editor.selecting do
349
+ move(:to, 1, 4)
350
+ end
351
+ editor.color_mask.should == [
352
+ nil,
353
+ [[2,262144], [4,0]],
354
+ nil,
355
+ ]
356
+ end
357
+
358
+ it "shows multi-line selection in scrolled space" do
359
+ write("\n\n\n\n\n0123456789\n0123456789\n0123456789\n\n")
360
+ editor.move(:to, 5,7)
361
+ editor.move(:relative, 0, 1)
362
+ editor.selecting do
363
+ move(:relative, 2, 1)
364
+ end
365
+ editor.view.should == "789\n789\n789\n"
366
+ editor.cursor.should == [2,2]
367
+ editor.color_mask.should == [
368
+ [[1,262144],[5,0]], # start to end of screen
369
+ [[0,262144],[5,0]], # 0 to end of screen
370
+ [[0,262144],[2,0]], # 0 to end of selection
371
+ ]
372
+ end
373
+ end
374
+
187
375
  describe :view do
188
376
  before do
189
377
  write('')
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruco
3
3
  version: !ruby/object:Gem::Version
4
- hash: 51
4
+ hash: 49
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 22
10
- version: 0.0.22
9
+ - 23
10
+ version: 0.0.23
11
11
  platform: ruby
12
12
  authors:
13
13
  - Michael Grosser
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-22 00:00:00 +01:00
18
+ date: 2011-01-23 00:00:00 +01:00
19
19
  default_executable: ruco
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -55,6 +55,7 @@ files:
55
55
  - lib/ruco/core_ext/array.rb
56
56
  - lib/ruco/core_ext/hash.rb
57
57
  - lib/ruco/core_ext/object.rb
58
+ - lib/ruco/core_ext/range.rb
58
59
  - lib/ruco/core_ext/string.rb
59
60
  - lib/ruco/cursor.rb
60
61
  - lib/ruco/editor.rb
@@ -66,6 +67,7 @@ files:
66
67
  - ruco.gemspec
67
68
  - spec/ruco/application_spec.rb
68
69
  - spec/ruco/command_bar_spec.rb
70
+ - spec/ruco/core_ext/array_spec.rb
69
71
  - spec/ruco/core_ext/string_spec.rb
70
72
  - spec/ruco/editor_spec.rb
71
73
  - spec/ruco/form_spec.rb
@@ -111,6 +113,7 @@ summary: Commandline editor written in ruby
111
113
  test_files:
112
114
  - spec/ruco/application_spec.rb
113
115
  - spec/ruco/command_bar_spec.rb
116
+ - spec/ruco/core_ext/array_spec.rb
114
117
  - spec/ruco/core_ext/string_spec.rb
115
118
  - spec/ruco/editor_spec.rb
116
119
  - spec/ruco/form_spec.rb