ruco 0.1.14 → 0.2.0.beta
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 +3 -0
- data/Gemfile.lock +11 -1
- data/Rakefile +27 -10
- data/Readme.md +14 -0
- data/VERSION +1 -1
- data/bin/ruco +15 -6
- data/lib/ruco.rb +25 -0
- data/lib/ruco/application.rb +1 -1
- data/lib/ruco/array_processor.rb +53 -0
- data/lib/ruco/core_ext/object.rb +26 -0
- data/lib/ruco/core_ext/range.rb +16 -1
- data/lib/ruco/editor.rb +1 -0
- data/lib/ruco/editor/colors.rb +97 -0
- data/lib/ruco/editor_area.rb +1 -0
- data/lib/ruco/file_store.rb +23 -5
- data/lib/ruco/screen.rb +61 -7
- data/lib/ruco/style_map.rb +6 -7
- data/lib/ruco/syntax_parser.rb +33 -0
- data/lib/ruco/tm_theme.rb +45 -0
- data/lib/ruco/window.rb +19 -14
- data/playground/benchmark_syntax_parser.rb +23 -0
- data/ruco.gemspec +21 -22
- data/spec/fixtures/railscasts.tmTheme +369 -0
- data/spec/fixtures/slow.js +4 -0
- data/spec/fixtures/test.tmTheme +186 -0
- data/spec/ruco/application_spec.rb +2 -2
- data/spec/ruco/array_processor_spec.rb +46 -0
- data/spec/ruco/command_bar_spec.rb +1 -1
- data/spec/ruco/core_ext/range_spec.rb +37 -0
- data/spec/ruco/editor_spec.rb +137 -19
- data/spec/ruco/file_store_spec.rb +45 -0
- data/spec/ruco/screen_spec.rb +39 -2
- data/spec/ruco/style_map_spec.rb +12 -1
- data/spec/ruco/syntax_parser_spec.rb +74 -0
- data/spec/ruco/tm_theme_spec.rb +14 -0
- data/spec/spec_helper.rb +6 -2
- metadata +57 -30
data/Gemfile
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
3
|
gem 'clipboard', '>=0.9.8'
|
4
|
+
gem 'ultraviolet1x'
|
5
|
+
gem 'language_sniffer'
|
4
6
|
|
5
7
|
group :dev do # not development <-> would add unneeded development dependencies in gemspec
|
8
|
+
gem 'oniguruma', :platform => :ruby_18
|
6
9
|
gem 'ffi', :platform => [:mingw]
|
7
10
|
gem 'rake'
|
8
11
|
gem 'rspec', '~>2'
|
data/Gemfile.lock
CHANGED
@@ -8,7 +8,10 @@ GEM
|
|
8
8
|
bundler (~> 1.0.0)
|
9
9
|
git (>= 1.2.5)
|
10
10
|
rake
|
11
|
-
|
11
|
+
language_sniffer (1.0.0)
|
12
|
+
oniguruma (1.1.0)
|
13
|
+
plist (3.1.0)
|
14
|
+
rake (0.9.2)
|
12
15
|
rspec (2.6.0)
|
13
16
|
rspec-core (~> 2.6.0)
|
14
17
|
rspec-expectations (~> 2.6.0)
|
@@ -17,6 +20,10 @@ GEM
|
|
17
20
|
rspec-expectations (2.6.0)
|
18
21
|
diff-lcs (~> 1.1.2)
|
19
22
|
rspec-mocks (2.6.0)
|
23
|
+
textpow1x (0.11.0)
|
24
|
+
plist (>= 3.0.1)
|
25
|
+
ultraviolet1x (0.12.1)
|
26
|
+
textpow1x (>= 0.11.0)
|
20
27
|
|
21
28
|
PLATFORMS
|
22
29
|
ruby
|
@@ -25,5 +32,8 @@ DEPENDENCIES
|
|
25
32
|
clipboard (>= 0.9.8)
|
26
33
|
ffi
|
27
34
|
jeweler
|
35
|
+
language_sniffer
|
36
|
+
oniguruma
|
28
37
|
rake
|
29
38
|
rspec (~> 2)
|
39
|
+
ultraviolet1x
|
data/Rakefile
CHANGED
@@ -21,19 +21,23 @@ end
|
|
21
21
|
|
22
22
|
task :try_color do
|
23
23
|
require 'curses'
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
# fix colors for xterm...
|
25
|
+
#if Curses::has_colors?
|
26
|
+
ENV['TERM'] += '-256color' if ENV['TERM'] == 'xterm'
|
27
|
+
Curses::start_color
|
28
|
+
# initialize every color we want to use
|
29
|
+
# id, foreground, background
|
30
|
+
#Curses.use_default_colors if defined? Curses.use_default_colors # 1.9 only, maybe helps to get real white...
|
31
|
+
Curses::init_pair( 32, 253, 39 )
|
32
|
+
#Curses::init_pair( 32, -1, -1 )
|
33
|
+
#Curses::init_pair( Curses::COLOR_RED, Curses::COLOR_RED, Curses::COLOR_BLACK )
|
34
|
+
#Curses::init_pair( Curses::COLOR_GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK )
|
35
|
+
#end
|
32
36
|
|
33
37
|
Curses.setpos(0,0)
|
34
|
-
Curses.attrset(Curses
|
38
|
+
Curses.attrset(Curses::color_pair( 32 )) # fetch color pair with the id xxx
|
35
39
|
Curses.addstr("xxxxxxxx\nyyyyyyy");
|
36
|
-
Curses.attrset(Curses
|
40
|
+
Curses.attrset(Curses::color_pair( 32 ))
|
37
41
|
Curses.addstr("xxxxxxxx\nyyyyyyy");
|
38
42
|
Curses.getch
|
39
43
|
end
|
@@ -61,6 +65,19 @@ task :key do
|
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
68
|
+
task :parse_syntax do
|
69
|
+
require 'ruco/array_processor'
|
70
|
+
require 'ultra_pow_list'
|
71
|
+
UltraPowList.make_loadable
|
72
|
+
require 'textpow'
|
73
|
+
require 'uv'
|
74
|
+
puts ruby = File.join(Uv.path.first,'uv', 'syntax','ruby.syntax')
|
75
|
+
syntax = Textpow::SyntaxNode.load(ruby)
|
76
|
+
processor = Ruco::ArrayProcessor.new
|
77
|
+
result = syntax.parse( "class Foo\n def xxx;end\nend", processor )
|
78
|
+
puts result.inspect
|
79
|
+
end
|
80
|
+
|
64
81
|
begin
|
65
82
|
require 'jeweler'
|
66
83
|
Jeweler::Tasks.new do |gem|
|
data/Readme.md
CHANGED
@@ -3,6 +3,7 @@ Simple, extendable, test-driven commandline editor written in ruby, for Linux/Ma
|
|
3
3
|
Features:
|
4
4
|
|
5
5
|
- **Intuitive interface**
|
6
|
+
- TextMate Syntax and Theme support
|
6
7
|
- selecting via Shift + arrow-keys (only Linux or iTerm) or 'select mode' Ctrl+b + arrow-keys
|
7
8
|
- move line up/down (Alt+Ctrl+up/down)
|
8
9
|
- Tab -> indent / Shift+Tab -> unindent
|
@@ -21,6 +22,9 @@ Features:
|
|
21
22
|
- (optional) blank line before eof on save
|
22
23
|
- (optional) line numbers
|
23
24
|
|
25
|
+
<br/>
|
26
|
+
(to get colors on ruby 1.8, install oniguruma [OsX](http://blog.pastie.org/2010/01/oniguruma-on-snow-leopard.html) / [Ubuntu](http://blog.loftninjas.org/2008/09/25/installing-the-oniguruma-gem-on-debianubuntu/))
|
27
|
+
|
24
28
|
Install
|
25
29
|
=======
|
26
30
|
sudo gem install ruco
|
@@ -40,6 +44,10 @@ Customize
|
|
40
44
|
options.editor_remove_trailing_whitespace_on_save = true # default false
|
41
45
|
options.editor_blank_line_before_eof_on_save = true # default false
|
42
46
|
options.editor_line_numbers = true # default false
|
47
|
+
|
48
|
+
# Use any Textmate theme e.g. from http://wiki.macromates.com/Themes/UserSubmittedThemes
|
49
|
+
# use a url that points directly to the theme, e.g. github 'raw' urls
|
50
|
+
options.color_theme = "https://raw.github.com/deplorableword/textmate-solarized/master/Solarized%20%28dark%29.tmTheme"
|
43
51
|
...
|
44
52
|
|
45
53
|
# bind a key
|
@@ -83,6 +91,12 @@ TIPS
|
|
83
91
|
|
84
92
|
TODO
|
85
93
|
=====
|
94
|
+
- only do syntax parsing for changed lines + selected lines <-> will not be redrawn anyhow
|
95
|
+
- try to use complete file coloring as removed in 26d6da4
|
96
|
+
- javascript syntax parsing is slow and often causes syntax-timeouts
|
97
|
+
- some languages are still not mapped correctly to their syntax file
|
98
|
+
[languages](https://github.com/grosser/language_sniffer/blob/master/lib/language_sniffer/languages.yml) <->
|
99
|
+
[syntaxes](https://github.com/grosser/ultraviolet/tree/master/syntax)
|
86
100
|
- do not fall back to 0:0 after undoing the first change
|
87
101
|
- check writable status every x seconds (e.g. in background) -> faster while typing
|
88
102
|
- search help e.g. 'Nothing found' '#4 of 6 hits' 'no more hits, start from beginning ?'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0.beta
|
data/bin/ruco
CHANGED
@@ -21,6 +21,7 @@ Options:
|
|
21
21
|
BANNER
|
22
22
|
opts.on("-c", "--convert-tabs","Convert tabs to spaces") { options[:convert_tabs] = true }
|
23
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 }
|
24
|
+
opts.on("-n", "--no-colors","No colors -- helps performance / broken terminals") { options[:no_colors] = true }
|
24
25
|
opts.on("--debug-cache","Show caching in action") { options[:debug_cache] = true }
|
25
26
|
opts.on("--debug-keys", "Show pressed keys") { options[:debug_keys] = true }
|
26
27
|
opts.on("-v", "--version","Show Version"){
|
@@ -44,15 +45,23 @@ def log(stuff)
|
|
44
45
|
File.open('ruco.log','ab'){|f| f.puts stuff }
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
+
options = parse_options
|
49
|
+
|
50
|
+
# do not use colors if told so or the terminal does not support colors
|
51
|
+
$ruco_colors = (
|
52
|
+
not options[:no_colors] and
|
53
|
+
RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ and # windows
|
54
|
+
ENV['TERM'] != 'rxvt' and
|
55
|
+
ENV['TERM'] !~ /-color$/
|
56
|
+
)
|
48
57
|
|
49
58
|
require 'ruco'
|
50
59
|
|
51
60
|
# draw app and redraw after each keystroke (or paste)
|
52
|
-
Ruco::Screen.open(
|
61
|
+
Ruco::Screen.open(options) do |screen|
|
53
62
|
app = Ruco::Application.new(ARGV[0],
|
54
|
-
:convert_tabs =>
|
55
|
-
:undo_stack_size =>
|
63
|
+
:convert_tabs => options[:convert_tabs],
|
64
|
+
:undo_stack_size => options[:undo_stack_size],
|
56
65
|
:lines => screen.lines, :columns => screen.columns
|
57
66
|
)
|
58
67
|
|
@@ -63,9 +72,9 @@ Ruco::Screen.open(@options) do |screen|
|
|
63
72
|
end
|
64
73
|
|
65
74
|
Keyboard.output do |key|
|
66
|
-
screen.debug_key(key) if
|
75
|
+
screen.debug_key(key) if options[:debug_keys]
|
67
76
|
if key == :resize
|
68
|
-
app.resize(lines, columns)
|
77
|
+
app.resize(screen.lines, screen.columns)
|
69
78
|
screen.clear_cache
|
70
79
|
else
|
71
80
|
result = app.key key
|
data/lib/ruco.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'language_sniffer'
|
2
|
+
|
1
3
|
require 'ruco/version'
|
2
4
|
|
3
5
|
require 'ruco/core_ext/object'
|
@@ -15,6 +17,7 @@ require 'ruco/file_store'
|
|
15
17
|
require 'ruco/window'
|
16
18
|
require 'ruco/screen'
|
17
19
|
require 'ruco/style_map'
|
20
|
+
require 'ruco/syntax_parser'
|
18
21
|
|
19
22
|
require 'ruco/editor'
|
20
23
|
require 'ruco/editor/line_numbers'
|
@@ -23,6 +26,28 @@ require 'ruco/status_bar'
|
|
23
26
|
require 'ruco/command_bar'
|
24
27
|
require 'ruco/application'
|
25
28
|
|
29
|
+
if $ruco_colors
|
30
|
+
begin
|
31
|
+
# this can fail on ruby 1.8 <-> oniguruma is complicated to install
|
32
|
+
require 'oniguruma' if RUBY_VERSION < '1.9.0'
|
33
|
+
|
34
|
+
# there are some other gems out there like spox-textpow etc, so be picky
|
35
|
+
gem 'plist'
|
36
|
+
require 'plist'
|
37
|
+
gem 'textpow1x'
|
38
|
+
require 'textpow'
|
39
|
+
gem 'ultraviolet1x'
|
40
|
+
require 'uv'
|
41
|
+
|
42
|
+
# we do not need there if any other color li failed
|
43
|
+
require 'ruco/array_processor'
|
44
|
+
require 'ruco/tm_theme'
|
45
|
+
require 'ruco/editor/colors'
|
46
|
+
rescue LoadError
|
47
|
+
warn "Could not load color libs -- #{$!}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
26
51
|
require 'ruco/form'
|
27
52
|
require 'ruco/text_area'
|
28
53
|
require 'ruco/editor_area'
|
data/lib/ruco/application.rb
CHANGED
@@ -247,7 +247,7 @@ module Ruco
|
|
247
247
|
@status_lines = 1
|
248
248
|
|
249
249
|
editor_options = @options.slice(
|
250
|
-
:columns, :convert_tabs, :convert_newlines, :undo_stack_size
|
250
|
+
:columns, :convert_tabs, :convert_newlines, :undo_stack_size, :color_theme
|
251
251
|
).merge(
|
252
252
|
:window => @options.nested(:window),
|
253
253
|
:history => @options.nested(:history),
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ruco
|
2
|
+
class ArrayProcessor
|
3
|
+
attr_accessor :lines
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@line_number = -1
|
7
|
+
@lines = []
|
8
|
+
@open_elements = []
|
9
|
+
@still_open_elements = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def open_tag(name, position)
|
13
|
+
#puts "Open #{name} #{@line_number}:#{position}"
|
14
|
+
@open_elements << [name, position]
|
15
|
+
end
|
16
|
+
|
17
|
+
def close_tag(name, position)
|
18
|
+
#puts "Close #{name} #{@line_number}:#{position}"
|
19
|
+
open_element = @open_elements.pop || @still_open_elements.pop
|
20
|
+
@lines[@line_number] << [name, open_element.last...position]
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_line(line)
|
24
|
+
#puts "Line #{line}"
|
25
|
+
# close elements only opened in last line
|
26
|
+
@open_elements.each do |name, position|
|
27
|
+
@lines[@line_number] << [name, position...@line.size]
|
28
|
+
end
|
29
|
+
|
30
|
+
# surround last line in still open elements from previouse lines
|
31
|
+
@still_open_elements.each do |name,_|
|
32
|
+
@lines[@line_number] << [name, 0...@line.size]
|
33
|
+
end
|
34
|
+
|
35
|
+
# mark open as 'still open'
|
36
|
+
# and let them start on column 0 -> if closed in this line its 0...position
|
37
|
+
@still_open_elements += @open_elements.map{|name,_| [name,0]}
|
38
|
+
@open_elements = []
|
39
|
+
|
40
|
+
# proceed with next line
|
41
|
+
@line = line
|
42
|
+
@line_number += 1
|
43
|
+
@lines[@line_number] = []
|
44
|
+
end
|
45
|
+
|
46
|
+
def start_parsing(name);end
|
47
|
+
def end_parsing(name);end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
@lines
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/ruco/core_ext/object.rb
CHANGED
@@ -50,3 +50,29 @@ class Object
|
|
50
50
|
Marshal.load(Marshal.dump(self))
|
51
51
|
end unless defined? deep_copy
|
52
52
|
end
|
53
|
+
|
54
|
+
class Object
|
55
|
+
# copy from active_support
|
56
|
+
def silence_warnings
|
57
|
+
with_warnings(nil) { yield }
|
58
|
+
end unless defined? silence_warnings
|
59
|
+
|
60
|
+
# copy from active_support
|
61
|
+
def with_warnings(flag)
|
62
|
+
old_verbose, $VERBOSE = $VERBOSE, flag
|
63
|
+
yield
|
64
|
+
ensure
|
65
|
+
$VERBOSE = old_verbose
|
66
|
+
end unless defined? with_warnings
|
67
|
+
end
|
68
|
+
|
69
|
+
# http://grosser.it/2010/07/23/open-uri-without-ssl-https-verification/
|
70
|
+
module OpenURI
|
71
|
+
def self.without_ssl_verification
|
72
|
+
old = ::OpenSSL::SSL::VERIFY_PEER
|
73
|
+
silence_warnings{ ::OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE }
|
74
|
+
yield
|
75
|
+
ensure
|
76
|
+
silence_warnings{ ::OpenSSL::SSL.const_set :VERIFY_PEER, old }
|
77
|
+
end
|
78
|
+
end
|
data/lib/ruco/core_ext/range.rb
CHANGED
@@ -3,4 +3,19 @@ class Range
|
|
3
3
|
def overlap?(other)
|
4
4
|
(first <= other.last) and (other.first <= last)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
|
+
# (1..2).last_element == 2
|
8
|
+
# (1...3).last_element == 2
|
9
|
+
def last_element
|
10
|
+
exclude_end? ? last.pred : last
|
11
|
+
end unless defined? last_element
|
12
|
+
|
13
|
+
# (1..2).move(2) == (3..4)
|
14
|
+
def move(n)
|
15
|
+
if exclude_end?
|
16
|
+
(first + n)...(last + n)
|
17
|
+
else
|
18
|
+
(first + n)..(last + n)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/ruco/editor.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Ruco
|
4
|
+
class Editor
|
5
|
+
module Colors
|
6
|
+
DEFAULT_THEME = 'spec/fixtures/railscasts.tmTheme'
|
7
|
+
STYLING_TIMEOUT = 4
|
8
|
+
|
9
|
+
def style_map
|
10
|
+
map = super
|
11
|
+
return map if @colors_took_too_long
|
12
|
+
|
13
|
+
# disable colors if syntax-parsing takes too long
|
14
|
+
begin
|
15
|
+
syntax = Timeout.timeout(STYLING_TIMEOUT) do
|
16
|
+
syntax_info[@window.visible_lines]
|
17
|
+
end
|
18
|
+
rescue Timeout::Error
|
19
|
+
# this takes too long, just go on without styles
|
20
|
+
STDERR.puts "Styling takes too long, go on without me!"
|
21
|
+
@colors_took_too_long = true
|
22
|
+
return map
|
23
|
+
end
|
24
|
+
|
25
|
+
add_syntax_highlighting_to_style_map(map, syntax)
|
26
|
+
|
27
|
+
if @selection
|
28
|
+
# add selection a second time so it stays on top
|
29
|
+
@window.add_selection_styles(map, @selection)
|
30
|
+
end
|
31
|
+
map
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def syntax_info
|
37
|
+
if language = @options[:language]
|
38
|
+
@syntax_info ||= {}
|
39
|
+
language = [language.name.downcase, language.lexer]
|
40
|
+
lines.map do |line|
|
41
|
+
@syntax_info[line] ||= SyntaxParser.syntax_for_lines([line], language).first
|
42
|
+
end
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_syntax_highlighting_to_style_map(map, syntax_info)
|
49
|
+
return unless syntax_info
|
50
|
+
|
51
|
+
$ruco_foreground = theme.foreground
|
52
|
+
$ruco_background = theme.background
|
53
|
+
|
54
|
+
syntax_info.each_with_index do |syntax_positions, line|
|
55
|
+
next unless syntax_positions
|
56
|
+
syntax_positions.each do |syntax_element, columns|
|
57
|
+
columns = columns.move(-@window.left)
|
58
|
+
style = style_for_syntax_element(syntax_element)
|
59
|
+
if style and columns.first >= 0
|
60
|
+
map.add(style, line, columns)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def style_for_syntax_element(syntax_element)
|
67
|
+
@style_for_element ||= {}
|
68
|
+
@style_for_element[syntax_element] ||= begin
|
69
|
+
_, style = theme.styles.detect{|name,style| syntax_element.start_with?(name) }
|
70
|
+
style
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def theme
|
75
|
+
@theme ||= begin
|
76
|
+
file = download_into_file(@options[:color_theme]) if @options[:color_theme]
|
77
|
+
file ||= DEFAULT_THEME
|
78
|
+
Ruco::TMTheme.new(file)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def download_into_file(url)
|
83
|
+
theme_store = FileStore.new(File.expand_path('~/.ruco/themes'), :keep => 5, :pure => true)
|
84
|
+
theme_store.cache(url) do
|
85
|
+
require 'open-uri'
|
86
|
+
require 'openssl'
|
87
|
+
OpenURI.without_ssl_verification do
|
88
|
+
open(url).read
|
89
|
+
end
|
90
|
+
end
|
91
|
+
File.expand_path(theme_store.file(url))
|
92
|
+
rescue => e
|
93
|
+
STDERR.puts "Could not download #{url} -- #{e}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|