ruco 0.0.22 → 0.0.23

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