ruco 0.0.56 → 0.1.0

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/Gemfile CHANGED
@@ -3,6 +3,7 @@ source :rubygems
3
3
  gem 'clipboard', '>=0.9.4'
4
4
 
5
5
  group :dev do # not development <-> would add unneeded development dependencies in gemspec
6
+ gem 'ffi', :platform => [:mingw]
6
7
  gem 'rake'
7
8
  gem 'rspec', '~>2'
8
9
  gem 'jeweler'
data/Gemfile.lock CHANGED
@@ -25,6 +25,7 @@ PLATFORMS
25
25
 
26
26
  DEPENDENCIES
27
27
  clipboard (>= 0.9.4)
28
+ ffi
28
29
  jeweler
29
30
  rake
30
31
  rspec (~> 2)
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ end
8
8
 
9
9
  task :run do
10
10
  file = 'spec/temp.txt'
11
- File.open(file, 'w'){|f|f.write("12345\n1234\n#{'abcdefg'*20}\n123")}
11
+ File.open(file, 'wb'){|f|f.write("12345\n1234\n#{'abcdefg'*20}\n123")}
12
12
  exec "./bin/ruco #{file}"
13
13
  end
14
14
 
@@ -19,6 +19,25 @@ task :try do
19
19
  Curses.getch
20
20
  end
21
21
 
22
+ task :try_color do
23
+ require 'curses'
24
+ if Curses::has_colors?
25
+ Curses::start_color
26
+ # initialize every color we want to use
27
+ # id, foreground, background
28
+ Curses::init_pair( Curses::COLOR_BLACK, Curses::COLOR_BLACK, Curses::COLOR_BLACK )
29
+ Curses::init_pair( Curses::COLOR_RED, Curses::COLOR_RED, Curses::COLOR_BLACK )
30
+ Curses::init_pair( Curses::COLOR_GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK )
31
+ end
32
+
33
+ Curses.setpos(0,0)
34
+ Curses.attrset(Curses.color_pair(Curses::COLOR_RED)) # fetch color pair with the id xxx
35
+ Curses.addstr("xxxxxxxx\nyyyyyyy");
36
+ Curses.attrset(Curses.color_pair(Curses::COLOR_GREEN))
37
+ Curses.addstr("xxxxxxxx\nyyyyyyy");
38
+ Curses.getch
39
+ end
40
+
22
41
  task :key do
23
42
  require 'curses'
24
43
 
data/Readme.md CHANGED
@@ -1,4 +1,4 @@
1
- Simple, extendable, test-driven commandline editor written in ruby.
1
+ Simple, extendable, test-driven commandline editor written in ruby, for Linux/Mac/Windows.
2
2
 
3
3
  Features:
4
4
 
@@ -81,6 +81,7 @@ TIPS
81
81
 
82
82
  TODO
83
83
  =====
84
+ - slow when opening file with 10,000+ short lines -> investigate
84
85
  - check writable status every x seconds (e.g. in background) -> faster while typing
85
86
  - search help e.g. 'Nothing found' '#4 of 6 hits' 'no more hits, start from beginning ?'
86
87
  - highlight current work when reopening search (so typing replaces it)
@@ -96,8 +97,13 @@ TODO
96
97
  - add auto-confirm to 'replace?' dialog -> type s == skip, no enter needed
97
98
  - 1.8: unicode support <-> already finished but unusable due to Curses (see encoding branch)
98
99
 
99
- Author
100
- ======
100
+ Authors
101
+ =======
102
+
103
+ ### [Contributors](http://github.com/grosser/ruco/contributors)
104
+ - [AJ Palkovic](https://github.com/ajpalkovic)
105
+
106
+
101
107
  [Michael Grosser](http://grosser.it)<br/>
102
108
  grosser.michael@gmail.com<br/>
103
109
  Hereby placed under public domain, do what you want, just do not hold me accountable...
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.56
1
+ 0.1.0
data/bin/ruco CHANGED
@@ -20,6 +20,7 @@ Usage:
20
20
  Options:
21
21
  BANNER
22
22
  opts.on("-c", "--convert-tabs","Convert tabs to spaces") { options[:convert_tabs] = true }
23
+ opts.on("-u", "--undo-stack-size SIZE","Maximum size of the undo stack. 0 allows for a complete undo stack.") {|size| options[:undo_stack_size] = size.to_i }
23
24
  opts.on("--debug-cache","Show caching in action") { options[:debug_cache] = true }
24
25
  opts.on("--debug-keys", "Show pressed keys") { options[:debug_keys] = true }
25
26
  opts.on("-v", "--version","Show Version"){
@@ -55,6 +56,7 @@ def init_screen
55
56
  begin
56
57
  yield
57
58
  ensure
59
+ Curses.clear # needed to clear the menu/status bar on windows
58
60
  Curses.close_screen
59
61
  end
60
62
  end
@@ -107,7 +109,7 @@ def debug_key(key)
107
109
  end
108
110
 
109
111
  def log(stuff)
110
- File.open('ruco.log','a'){|f| f.puts stuff }
112
+ File.open('ruco.log','ab'){|f| f.puts stuff }
111
113
  end
112
114
 
113
115
  @options = parse_options
@@ -116,6 +118,7 @@ require 'ruco'
116
118
 
117
119
  app = Ruco::Application.new(ARGV[0],
118
120
  :convert_tabs => @options[:convert_tabs],
121
+ :undo_stack_size => @options[:undo_stack_size],
119
122
  :lines => Curses.stdscr.maxy, :columns => Curses.stdscr.maxx
120
123
  )
121
124
 
@@ -155,7 +155,7 @@ module Ruco
155
155
 
156
156
  action :quit do
157
157
  if editor.modified?
158
- ask("Loose changes? Enter=Yes Esc=Cancel") do
158
+ ask("Lose changes? Enter=Yes Esc=Cancel") do
159
159
  editor.store_session
160
160
  :quit
161
161
  end
@@ -253,7 +253,7 @@ module Ruco
253
253
  @status_lines = 1
254
254
 
255
255
  editor_options = @options.slice(
256
- :columns, :convert_tabs, :convert_newlines
256
+ :columns, :convert_tabs, :convert_newlines, :undo_stack_size
257
257
  ).merge(
258
258
  :window => @options.nested(:window),
259
259
  :history => @options.nested(:history),
@@ -1,5 +1,17 @@
1
1
  class File
2
2
  def self.write(to, content)
3
- File.open(to, 'w'){|f| f.write(content) }
3
+ File.open(to, 'wb'){|f| f.write(content) }
4
+ end
5
+
6
+ # Open files in binary mode. On linux this is ignored by ruby.
7
+ # On Windows ruby open files in text mode by default, so it replaces \r with \n,
8
+ # so the specs fail. If files are opened in binary mode, which is the only mode
9
+ # on linux, it does not replace the newlines. This thread has slightly more information:
10
+ # http://groups.google.com/group/rubyinstaller/browse_thread/thread/c7fbe346831e58cc
11
+ def self.binary_read(file)
12
+ io = File.open(file, 'rb')
13
+ content = io.read
14
+ io.close
15
+ content
4
16
  end
5
17
  end
data/lib/ruco/editor.rb CHANGED
@@ -2,10 +2,11 @@ module Ruco
2
2
  class Editor
3
3
  attr_reader :file
4
4
  attr_reader :text_area
5
+ attr_reader :history
5
6
  private :text_area
6
7
  delegate :view, :style_map, :cursor, :position,
7
8
  :insert, :indent, :unindent, :delete, :delete_line,
8
- :redo, :undo,
9
+ :redo, :undo, :save_state,
9
10
  :selecting, :selection, :text_in_selection, :reset,
10
11
  :move, :resize, :move_line,
11
12
  :to => :text_area
@@ -19,7 +20,7 @@ module Ruco
19
20
  raise "#{@file} is larger than 1MB, did you really want to open that with Ruco?"
20
21
  end
21
22
 
22
- content = (File.exist?(@file) ? File.read(@file) : '')
23
+ content = (File.exist?(@file) ? File.binary_read(@file) : '')
23
24
  content.tabs_to_spaces! if @options[:convert_tabs]
24
25
 
25
26
  # cleanup newline formats
@@ -29,6 +30,7 @@ module Ruco
29
30
 
30
31
  @saved_content = content
31
32
  @text_area = EditorArea.new(content, @options)
33
+ @history = @text_area.history
32
34
  restore_session
33
35
  end
34
36
 
@@ -51,7 +53,7 @@ module Ruco
51
53
  lines << '' if @options[:blank_line_before_eof_on_save] and lines.last.to_s !~ /^\s*$/
52
54
  content = lines * @newline
53
55
 
54
- File.open(@file,'w'){|f| f.write(content) }
56
+ File.open(@file,'wb'){|f| f.write(content) }
55
57
  @saved_content = content.gsub(/\r?\n/, "\n")
56
58
 
57
59
  true
@@ -1,9 +1,11 @@
1
1
  module Ruco
2
2
  class Editor
3
3
  module History
4
+ attr_reader :history
5
+
4
6
  def initialize(content, options)
5
7
  super(content, options)
6
- @history = Ruco::History.new((options[:history]||{}).reverse_merge(:state => state, :track => [:content], :entries => 100, :timeout => 2))
8
+ @history = Ruco::History.new((options[:history]||{}).reverse_merge(:state => state, :track => [:content], :entries => options[:undo_stack_size], :timeout => 2))
7
9
  end
8
10
 
9
11
  def undo
@@ -15,7 +17,7 @@ module Ruco
15
17
  @history.redo
16
18
  self.state = @history.state
17
19
  end
18
-
20
+
19
21
  def view
20
22
  @history.add(state)
21
23
  super
File without changes
@@ -1,4 +1,5 @@
1
1
  require "digest/md5"
2
+ require "fileutils"
2
3
 
3
4
  module Ruco
4
5
  class FileStore
@@ -8,21 +9,27 @@ module Ruco
8
9
  end
9
10
 
10
11
  def set(key, value)
11
- `mkdir -p #{@folder}` unless File.exist? @folder
12
+ FileUtils.mkdir_p @folder unless File.exist? @folder
12
13
  File.write(file(key), serialize(value))
13
14
  cleanup
14
15
  end
15
16
 
16
17
  def get(key)
17
18
  file = file(key)
18
- deserialize File.read(file) if File.exist?(file)
19
+ deserialize File.binary_read(file) if File.exist?(file)
19
20
  end
20
21
 
21
22
  private
22
23
 
24
+ def entries
25
+ (Dir.entries(@folder) - ['.','..']).
26
+ map{|entry| File.join(@folder, entry) }.
27
+ sort_by{|file| File.mtime(file) }
28
+ end
29
+
23
30
  def cleanup
24
- delete = `ls -t #{@folder}`.split("\n")[@options[:keep]..-1] || []
25
- delete.each{|f| File.delete("#{@folder}/#{f}") }
31
+ delete = entries[0...-@options[:keep]] || []
32
+ delete.each{|f| File.delete(f) }
26
33
  end
27
34
 
28
35
  def file(key)
@@ -37,4 +44,4 @@ module Ruco
37
44
  Marshal.load(value)
38
45
  end
39
46
  end
40
- end
47
+ end
data/lib/ruco/history.rb CHANGED
@@ -1,41 +1,65 @@
1
1
  module Ruco
2
2
  class History
3
3
  attr_accessor :timeout
4
+ attr_reader :stack, :position
4
5
 
5
6
  def initialize(options)
6
7
  @options = options
7
- @stack = [@options.delete(:state)]
8
+ @options[:entries] ||= 100
8
9
  @timeout = options.delete(:timeout) || 0
9
- timeout!
10
+
11
+ @stack = [{:mutable => false, :created_at => 0, :type => :initial, :state => @options.delete(:state)}]
10
12
  @position = 0
11
13
  end
12
14
 
13
15
  def state
14
- @stack[@position]
16
+ @stack[@position][:state]
15
17
  end
16
18
 
17
19
  def add(state)
18
20
  return unless tracked_field_changes?(state)
19
21
  remove_undone_states
20
- if merge_timeout?
22
+ unless merge? state
23
+ # can no longer modify previous states
24
+ @stack[@position][:mutable] = false
25
+
26
+ state_type = type(state)
21
27
  @position += 1
22
- @last_merge = Time.now.to_f
28
+ @stack[@position] = {:mutable => true, :type => state_type, :created_at => Time.now.to_f}
23
29
  end
24
- @stack[@position] = state
30
+ @stack[@position][:state] = state
25
31
  limit_stack
26
32
  end
27
33
 
28
34
  def undo
29
- timeout!
30
35
  @position = [@position - 1, 0].max
31
36
  end
32
37
 
33
38
  def redo
34
- timeout!
35
39
  @position = [@position + 1, @stack.size - 1].min
36
40
  end
37
41
 
38
42
  private
43
+ def type(state)
44
+ @options[:track].each do |field|
45
+ if state[field].is_a?(String) && @stack[@position][:state][field].is_a?(String)
46
+ diff = state[field].length - @stack[@position][:state][field].length
47
+ if diff > 0
48
+ return :insert
49
+ elsif diff < 0
50
+ return :delete
51
+ end
52
+ end
53
+ end
54
+ nil
55
+ end
56
+
57
+ def merge?(state)
58
+ top = @stack[@position]
59
+ top[:mutable] &&
60
+ top[:type] == type(state) &&
61
+ top[:created_at]+@timeout > Time.now.to_f
62
+ end
39
63
 
40
64
  def remove_undone_states
41
65
  @stack.slice!(@position + 1, 9999999)
@@ -48,18 +72,11 @@ module Ruco
48
72
  end
49
73
 
50
74
  def limit_stack
75
+ return if @options[:entries] == 0
51
76
  to_remove = @stack.size - @options[:entries]
52
77
  return if to_remove < 1
53
78
  @stack.slice!(0, to_remove)
54
79
  @position -= to_remove
55
80
  end
56
-
57
- def timeout!
58
- @last_merge = 0
59
- end
60
-
61
- def merge_timeout?
62
- (Time.now.to_f - @last_merge) > @timeout
63
- end
64
81
  end
65
82
  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.56"
8
+ s.version = "0.1.0"
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-05-11}
12
+ s.date = %q{2011-05-20}
13
13
  s.default_executable = %q{ruco}
14
14
  s.email = %q{michael@grosser.it}
15
15
  s.executables = ["ruco"]
@@ -15,11 +15,11 @@ describe Ruco::Application do
15
15
  end
16
16
 
17
17
  def write(content)
18
- File.open(@file,'w'){|f| f.write(content) }
18
+ File.open(@file,'wb'){|f| f.write(content) }
19
19
  end
20
20
 
21
21
  def read
22
- File.read(@file)
22
+ File.binary_read(@file)
23
23
  end
24
24
 
25
25
  def editor_part(view)
@@ -110,7 +110,7 @@ describe Ruco::Application do
110
110
  it "asks before closing changed file -- escape == no" do
111
111
  app.key('a')
112
112
  app.key(:"Ctrl+w")
113
- app.view.split("\n").last.should include("Loose changes")
113
+ app.view.split("\n").last.should include("Lose changes")
114
114
  app.key(:escape).should_not == :quit
115
115
  app.key("\n").should_not == :quit
116
116
  end
@@ -118,7 +118,7 @@ describe Ruco::Application do
118
118
  it "asks before closing changed file -- enter == yes" do
119
119
  app.key('a')
120
120
  app.key(:"Ctrl+w")
121
- app.view.split("\n").last.should include("Loose changes")
121
+ app.view.split("\n").last.should include("Lose changes")
122
122
  app.key(:enter).should == :quit
123
123
  end
124
124
  end
@@ -2,11 +2,11 @@ require File.expand_path('spec/spec_helper')
2
2
 
3
3
  describe Ruco::Editor do
4
4
  def write(content)
5
- File.open(@file,'w'){|f| f.write(content) }
5
+ File.open(@file,'wb'){|f| f.write(content) }
6
6
  end
7
7
 
8
8
  def read
9
- File.read(@file)
9
+ File.binary_read(@file)
10
10
  end
11
11
 
12
12
  let(:editor){
@@ -753,6 +753,25 @@ describe Ruco::Editor do
753
753
  end
754
754
 
755
755
  describe 'history' do
756
+ it "does not overwrite the initial state" do
757
+ write("a")
758
+ editor.insert("b")
759
+ editor.view # trigger save point
760
+ stack = editor.history.stack
761
+ stack.length.should == 2
762
+ stack[0][:state][:content].should == "a"
763
+ stack[1][:state][:content].should == "ba"
764
+
765
+ editor.undo
766
+ editor.history.position.should == 0
767
+
768
+ editor.insert("c")
769
+ editor.view # trigger save point
770
+ stack.length.should == 2
771
+ stack[0][:state][:content].should == "a"
772
+ stack[1][:state][:content].should == "ca"
773
+ end
774
+
756
775
  it "can undo an action" do
757
776
  write("a")
758
777
  editor.insert("b")
@@ -768,6 +787,7 @@ describe Ruco::Editor do
768
787
 
769
788
  it "removes selection on undo" do
770
789
  editor.insert('a')
790
+ editor.view # trigger save point
771
791
  editor.selecting{move(:to, 1,1)}
772
792
  editor.selection.should_not == nil
773
793
  editor.view # trigger save point
@@ -777,7 +797,6 @@ describe Ruco::Editor do
777
797
 
778
798
  it "sets modified on undo" do
779
799
  editor.insert('a')
780
- editor.view # trigger save point
781
800
  editor.save
782
801
  editor.modified?.should == false
783
802
  editor.undo
@@ -790,14 +809,14 @@ describe Ruco::Editor do
790
809
  write('xxx')
791
810
  editor.insert('a')
792
811
  editor.save.should == true
793
- File.read(@file).should == 'axxx'
812
+ File.binary_read(@file).should == 'axxx'
794
813
  end
795
814
 
796
815
  it 'creates the file' do
797
816
  `rm #{@file}`
798
817
  editor.insert('a')
799
818
  editor.save.should == true
800
- File.read(@file).should == 'a'
819
+ File.binary_read(@file).should == 'a'
801
820
  end
802
821
 
803
822
  it 'does not crash when it cannot save a file' do
@@ -1,6 +1,10 @@
1
1
  require File.expand_path('spec/spec_helper')
2
2
 
3
3
  describe Ruco::FileStore do
4
+ def mark_all_as_old
5
+ store.send(:entries).each{|e| File.utime(1,1,e) }
6
+ end
7
+
4
8
  before do
5
9
  @folder = 'spec/sessions'
6
10
  `rm -rf #{@folder}`
@@ -21,23 +25,10 @@ describe Ruco::FileStore do
21
25
  store.set('xxx', 1)
22
26
  store.set('yyy', 1)
23
27
  store.set('zzz', 1)
24
- store.set('aaa', 1)
25
- store.get('xxx').should == nil
26
- end
27
-
28
- it "drops least recently used key" do
29
- store.set('xxx', 1)
30
- sleep(0.1)
31
- store.set('yyy', 1)
32
- sleep(0.1)
33
- store.set('xxx', 1)
34
- sleep(0.1)
35
- store.set('zzz', 1)
36
- sleep(0.1)
37
- store.set('aaa', 1)
38
- sleep(0.1)
39
- store.get('xxx').should == 1
40
- store.get('yyy').should == nil
28
+ mark_all_as_old
29
+ store.set('aaa', 2)
30
+ store.get('aaa').should == 2
31
+ ['xxx','yyy','zzz'].map{|f| store.get(f) }.should =~ [1,1,nil]
41
32
  end
42
33
 
43
34
  it "does not drop if used multiple times" do
@@ -45,6 +36,7 @@ describe Ruco::FileStore do
45
36
  store.set('yyy', 1)
46
37
  store.set('zzz', 1)
47
38
  store.set('zzz', 1)
39
+ mark_all_as_old
48
40
  store.set('zzz', 1)
49
41
  store.set('zzz', 1)
50
42
  store.get('xxx').should == 1
@@ -74,6 +74,20 @@ describe Ruco::History do
74
74
  history.undo
75
75
  history.state.should == {:x => 3}
76
76
  end
77
+
78
+ describe 'with strings' do
79
+ let(:history){ Ruco::History.new(:state => {:x => 'a'}, :track => [:x], :timeout => 0.1) }
80
+
81
+ it "triggers a new state on insertion and deletion" do
82
+ %w{ab abc ab a a}.each{|state| history.add(:x => state)}
83
+
84
+ history.undo
85
+ history.state.should == {:x => "abc"}
86
+
87
+ history.undo
88
+ history.state.should == {:x => "a"}
89
+ end
90
+ end
77
91
 
78
92
  describe 'with timeout' do
79
93
  let(:history){ Ruco::History.new(:state => {:x => 1}, :track => [:x], :entries => 3, :timeout => 0.1) }
@@ -114,4 +128,16 @@ describe Ruco::History do
114
128
  history.state.should == {:x => 3}
115
129
  end
116
130
  end
131
+
132
+ describe 'with no entry limit' do
133
+ let(:history){ Ruco::History.new(:state => {:x => 1}, :track => [:x], :entries => 0, :timeout => 0) }
134
+
135
+ it "should track unlimited states" do
136
+ 200.times do |i|
137
+ history.add(:x => i+5)
138
+ end
139
+ history.stack.length.should == 201
140
+ end
141
+ end
142
+
117
143
  end
@@ -29,8 +29,11 @@ describe Ruco::StatusBar do
29
29
  end
30
30
 
31
31
  it "indicates not writable" do
32
- editor.stub!(:file).and_return '/etc/sudoers'
33
- bar.view.should include('!')
32
+ # this test will always fail on Windows with cygwin because of how cygwin sets up permissions
33
+ unless RUBY_PLATFORM =~ /mingw/
34
+ editor.stub!(:file).and_return '/etc/sudoers'
35
+ bar.view.should include('!')
36
+ end
34
37
  end
35
38
 
36
39
  it "shows line and column and right side" do
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: 111
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 56
10
- version: 0.0.56
10
+ version: 0.1.0
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-05-11 00:00:00 +02:00
18
+ date: 2011-05-20 00:00:00 +02:00
19
19
  default_executable: ruco
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency