green_shoes 0.227.0 → 0.233.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -0
- data/VERSION +1 -1
- data/lib/ext/highlighter.rb +2 -0
- data/lib/ext/highlighter/common.rb +197 -0
- data/lib/ext/highlighter/lang/ruby.rb +316 -0
- data/lib/ext/highlighter/markup.rb +222 -0
- data/lib/green_shoes.rb +1 -0
- data/lib/shoes/basic.rb +2 -0
- data/lib/shoes/help.rb +7 -2
- data/lib/shoes/helper_methods.rb +2 -1
- data/lib/shoes/main.rb +7 -2
- data/lib/shoes/ruby.rb +4 -0
- data/lib/shoes/slot.rb +7 -3
- data/samples/sample52.rb +32 -0
- data/samples/sample53.rb +10 -0
- data/samples/treeview.rb +60 -0
- data/snapshots/sample52.png +0 -0
- data/snapshots/sample53.png +0 -0
- data/static/manual-en.txt +31 -0
- metadata +12 -3
data/README.md
CHANGED
@@ -68,6 +68,7 @@ Except:
|
|
68
68
|
- hh/static/(all).png (c) 2008 why the lucky stiff
|
69
69
|
- lib/ext/hpricot/(all) (c) 2008 why the lucky stiff
|
70
70
|
- lib/ext/projector/(all).rb (c) 2010 MIZUTANI Tociyuki
|
71
|
+
- lib/ext/highlighter/(all) (c) 2008 why the lucky stiff and 2011 Steve Klabnik
|
71
72
|
- samples/akatsukiface.png (c) 2010 MIZUTANI Tociyuki
|
72
73
|
- samples/class-book.yaml (c) 2008 why the lucky stiff
|
73
74
|
- samples/splash-hand.png (c) 2008 why the lucky stiff
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.233.0
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module HH::Syntax
|
4
|
+
|
5
|
+
# A single token extracted by a tokenizer. It is simply the lexeme
|
6
|
+
# itself, decorated with a 'group' attribute to identify the type of the
|
7
|
+
# lexeme.
|
8
|
+
class Token < String
|
9
|
+
|
10
|
+
# the type of the lexeme that was extracted.
|
11
|
+
attr_reader :group
|
12
|
+
|
13
|
+
# the instruction associated with this token (:none, :region_open, or
|
14
|
+
# :region_close)
|
15
|
+
attr_reader :instruction
|
16
|
+
|
17
|
+
# Create a new Token representing the given text, and belonging to the
|
18
|
+
# given group.
|
19
|
+
def initialize( text, group, instruction = :none )
|
20
|
+
super text
|
21
|
+
@group = group
|
22
|
+
@instruction = instruction
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# The base class of all tokenizers. It sets up the scanner and manages the
|
28
|
+
# looping until all tokens have been extracted. It also provides convenience
|
29
|
+
# methods to make sure adjacent tokens of identical groups are returned as
|
30
|
+
# a single token.
|
31
|
+
class Tokenizer
|
32
|
+
|
33
|
+
# The current group being processed by the tokenizer
|
34
|
+
attr_reader :group
|
35
|
+
|
36
|
+
# The current chunk of text being accumulated
|
37
|
+
attr_reader :chunk
|
38
|
+
|
39
|
+
# Start tokenizing. This sets up the state in preparation for tokenization,
|
40
|
+
# such as creating a new scanner for the text and saving the callback block.
|
41
|
+
# The block will be invoked for each token extracted.
|
42
|
+
def start( text, &block )
|
43
|
+
@chunk = ""
|
44
|
+
@group = :normal
|
45
|
+
@callback = block
|
46
|
+
@text = StringScanner.new( text )
|
47
|
+
setup
|
48
|
+
end
|
49
|
+
|
50
|
+
# Subclasses may override this method to provide implementation-specific
|
51
|
+
# setup logic.
|
52
|
+
def setup
|
53
|
+
end
|
54
|
+
|
55
|
+
# Finish tokenizing. This flushes the buffer, yielding any remaining text
|
56
|
+
# to the client.
|
57
|
+
def finish
|
58
|
+
start_group nil
|
59
|
+
teardown
|
60
|
+
end
|
61
|
+
|
62
|
+
# Subclasses may override this method to provide implementation-specific
|
63
|
+
# teardown logic.
|
64
|
+
def teardown
|
65
|
+
end
|
66
|
+
|
67
|
+
# Subclasses must implement this method, which is called for each iteration
|
68
|
+
# of the tokenization process. This method may extract multiple tokens.
|
69
|
+
def step
|
70
|
+
raise NotImplementedError, "subclasses must implement #step"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Begins tokenizing the given text, calling #step until the text has been
|
74
|
+
# exhausted.
|
75
|
+
def tokenize( text, &block )
|
76
|
+
start text, &block
|
77
|
+
step until @text.eos?
|
78
|
+
finish
|
79
|
+
end
|
80
|
+
|
81
|
+
# Specify a set of tokenizer-specific options. Each tokenizer may (or may
|
82
|
+
# not) publish any options, but if a tokenizer does those options may be
|
83
|
+
# used to specify optional behavior.
|
84
|
+
def set( opts={} )
|
85
|
+
( @options ||= Hash.new ).update opts
|
86
|
+
end
|
87
|
+
|
88
|
+
# Get the value of the specified option.
|
89
|
+
def option(opt)
|
90
|
+
@options ? @options[opt] : nil
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
EOL = /(?=\r\n?|\n|$)/
|
96
|
+
|
97
|
+
# A convenience for delegating method calls to the scanner.
|
98
|
+
def self.delegate( sym )
|
99
|
+
define_method( sym ) { |*a| @text.__send__( sym, *a ) }
|
100
|
+
end
|
101
|
+
|
102
|
+
delegate :bol?
|
103
|
+
delegate :eos?
|
104
|
+
delegate :scan
|
105
|
+
delegate :scan_until
|
106
|
+
delegate :check
|
107
|
+
delegate :check_until
|
108
|
+
delegate :getch
|
109
|
+
delegate :matched
|
110
|
+
delegate :pre_match
|
111
|
+
delegate :peek
|
112
|
+
delegate :pos
|
113
|
+
|
114
|
+
# Access the n-th subgroup from the most recent match.
|
115
|
+
def subgroup(n)
|
116
|
+
@text[n]
|
117
|
+
end
|
118
|
+
|
119
|
+
# Append the given data to the currently active chunk.
|
120
|
+
def append( data )
|
121
|
+
@chunk << data
|
122
|
+
end
|
123
|
+
|
124
|
+
# Request that a new group be started. If the current group is the same
|
125
|
+
# as the group being requested, a new group will not be created. If a new
|
126
|
+
# group is created and the current chunk is not empty, the chunk's
|
127
|
+
# contents will be yielded to the client as a token, and then cleared.
|
128
|
+
#
|
129
|
+
# After the new group is started, if +data+ is non-nil it will be appended
|
130
|
+
# to the chunk.
|
131
|
+
def start_group( gr, data=nil )
|
132
|
+
flush_chunk if gr != @group
|
133
|
+
@group = gr
|
134
|
+
@chunk << data if data
|
135
|
+
end
|
136
|
+
|
137
|
+
def start_region( gr, data=nil )
|
138
|
+
flush_chunk
|
139
|
+
@group = gr
|
140
|
+
@callback.call( Token.new( data||"", @group, :region_open ) )
|
141
|
+
end
|
142
|
+
|
143
|
+
def end_region( gr, data=nil )
|
144
|
+
flush_chunk
|
145
|
+
@group = gr
|
146
|
+
@callback.call( Token.new( data||"", @group, :region_close ) )
|
147
|
+
end
|
148
|
+
|
149
|
+
def flush_chunk
|
150
|
+
@callback.call( Token.new( @chunk, @group ) ) unless @chunk.empty?
|
151
|
+
@chunk = ""
|
152
|
+
end
|
153
|
+
|
154
|
+
def subtokenize( syntax, text )
|
155
|
+
tokenizer = Syntax.load( syntax )
|
156
|
+
tokenizer.set @options if @options
|
157
|
+
flush_chunk
|
158
|
+
tokenizer.tokenize( text, &@callback )
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
# A default tokenizer for handling syntaxes that are not explicitly handled
|
164
|
+
# elsewhere. It simply yields the given text as a single token.
|
165
|
+
class Default
|
166
|
+
|
167
|
+
# Yield the given text as a single token.
|
168
|
+
def tokenize( text )
|
169
|
+
yield Token.new( text, :normal )
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
# A hash for registering syntax implementations.
|
175
|
+
SYNTAX = Hash.new( Default )
|
176
|
+
|
177
|
+
# Load the implementation of the requested syntax. If the syntax cannot be
|
178
|
+
# found, or if it cannot be loaded for whatever reason, the Default syntax
|
179
|
+
# handler will be returned.
|
180
|
+
def load( syntax )
|
181
|
+
begin
|
182
|
+
require_relative "lang/#{syntax}"
|
183
|
+
rescue LoadError
|
184
|
+
end
|
185
|
+
SYNTAX[ syntax ].new
|
186
|
+
end
|
187
|
+
module_function :load
|
188
|
+
|
189
|
+
# Return an array of the names of supported syntaxes.
|
190
|
+
def all
|
191
|
+
lang_dir = File.join(File.dirname(__FILE__), "syntax", "lang")
|
192
|
+
Dir["#{lang_dir}/*.rb"].map { |path| File.basename(path, ".rb") }
|
193
|
+
end
|
194
|
+
module_function :all
|
195
|
+
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
|
2
|
+
module HH::Syntax
|
3
|
+
|
4
|
+
# A tokenizer for the Ruby language. It recognizes all common syntax
|
5
|
+
# (and some less common syntax) but because it is not a true lexer, it
|
6
|
+
# will make mistakes on some ambiguous cases.
|
7
|
+
class Ruby < Tokenizer
|
8
|
+
|
9
|
+
# The list of all identifiers recognized as keywords.
|
10
|
+
KEYWORDS =
|
11
|
+
%w{if then elsif else end begin do rescue ensure while for
|
12
|
+
class module def yield raise until unless and or not when
|
13
|
+
case super undef break next redo retry in return alias
|
14
|
+
defined?}
|
15
|
+
|
16
|
+
# Perform ruby-specific setup
|
17
|
+
def setup
|
18
|
+
@selector = false
|
19
|
+
@allow_operator = false
|
20
|
+
@heredocs = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Step through a single iteration of the tokenization process.
|
24
|
+
def step
|
25
|
+
case
|
26
|
+
when bol? && check( /=begin/ )
|
27
|
+
start_group( :comment, scan_until( /^=end#{EOL}/ ) )
|
28
|
+
when bol? && check( /__END__#{EOL}/ )
|
29
|
+
start_group( :comment, scan_until( /\Z/ ) )
|
30
|
+
else
|
31
|
+
case
|
32
|
+
when check( /def\s+/ )
|
33
|
+
start_group :keyword, scan( /def\s+/ )
|
34
|
+
start_group :method, scan_until( /(?=[;(\s]|#{EOL})/ )
|
35
|
+
when check( /class\s+/ )
|
36
|
+
start_group :keyword, scan( /class\s+/ )
|
37
|
+
start_group :class, scan_until( /(?=[;\s<]|#{EOL})/ )
|
38
|
+
when check( /module\s+/ )
|
39
|
+
start_group :keyword, scan( /module\s+/ )
|
40
|
+
start_group :module, scan_until( /(?=[;\s]|#{EOL})/ )
|
41
|
+
when check( /::/ )
|
42
|
+
start_group :punct, scan(/::/)
|
43
|
+
when check( /:"/ )
|
44
|
+
start_group :symbol, scan(/:/)
|
45
|
+
scan_delimited_region :symbol, :symbol, "", true
|
46
|
+
@allow_operator = true
|
47
|
+
when check( /:'/ )
|
48
|
+
start_group :symbol, scan(/:/)
|
49
|
+
scan_delimited_region :symbol, :symbol, "", false
|
50
|
+
@allow_operator = true
|
51
|
+
when scan( /:[_a-zA-Z@$][$@\w]*[=!?]?/ )
|
52
|
+
start_group :symbol, matched
|
53
|
+
@allow_operator = true
|
54
|
+
when scan( /\?(\\[^\n\r]|[^\\\n\r\s])/ )
|
55
|
+
start_group :char, matched
|
56
|
+
@allow_operator = true
|
57
|
+
when check( /(__FILE__|__LINE__|true|false|nil|self)[?!]?/ )
|
58
|
+
if @selector || matched[-1] == ?? || matched[-1] == ?!
|
59
|
+
start_group :ident,
|
60
|
+
scan(/(__FILE__|__LINE__|true|false|nil|self)[?!]?/)
|
61
|
+
else
|
62
|
+
start_group :constant,
|
63
|
+
scan(/(__FILE__|__LINE__|true|false|nil|self)/)
|
64
|
+
end
|
65
|
+
@selector = false
|
66
|
+
@allow_operator = true
|
67
|
+
when scan(/0([bB][01]+|[oO][0-7]+|[dD][0-9]+|[xX][0-9a-fA-F]+)/)
|
68
|
+
start_group :number, matched
|
69
|
+
@allow_operator = true
|
70
|
+
else
|
71
|
+
case peek(2)
|
72
|
+
when "%r"
|
73
|
+
scan_delimited_region :punct, :regex, scan( /../ ), true
|
74
|
+
@allow_operator = true
|
75
|
+
when "%w", "%q"
|
76
|
+
scan_delimited_region :punct, :string, scan( /../ ), false
|
77
|
+
@allow_operator = true
|
78
|
+
when "%s"
|
79
|
+
scan_delimited_region :punct, :symbol, scan( /../ ), false
|
80
|
+
@allow_operator = true
|
81
|
+
when "%W", "%Q", "%x"
|
82
|
+
scan_delimited_region :punct, :string, scan( /../ ), true
|
83
|
+
@allow_operator = true
|
84
|
+
when /%[^\sa-zA-Z0-9]/
|
85
|
+
scan_delimited_region :punct, :string, scan( /./ ), true
|
86
|
+
@allow_operator = true
|
87
|
+
when "<<"
|
88
|
+
saw_word = ( chunk[-1,1] =~ /[\w!?]/ )
|
89
|
+
start_group :punct, scan( /<</ )
|
90
|
+
if saw_word
|
91
|
+
@allow_operator = false
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
float_right = scan( /-/ )
|
96
|
+
append "-" if float_right
|
97
|
+
if ( type = scan( /['"]/ ) )
|
98
|
+
append type
|
99
|
+
delim = scan_until( /(?=#{type})/ )
|
100
|
+
if delim.nil?
|
101
|
+
append scan_until( /\Z/ )
|
102
|
+
return
|
103
|
+
end
|
104
|
+
else
|
105
|
+
delim = scan( /\w+/ ) or return
|
106
|
+
end
|
107
|
+
start_group :constant, delim
|
108
|
+
start_group :punct, scan( /#{type}/ ) if type
|
109
|
+
@heredocs << [ float_right, type, delim ]
|
110
|
+
@allow_operator = true
|
111
|
+
else
|
112
|
+
case peek(1)
|
113
|
+
when /[\n\r]/
|
114
|
+
unless @heredocs.empty?
|
115
|
+
scan_heredoc(*@heredocs.shift)
|
116
|
+
else
|
117
|
+
start_group :normal, scan( /\s+/ )
|
118
|
+
end
|
119
|
+
@allow_operator = false
|
120
|
+
when /\s/
|
121
|
+
start_group :normal, scan( /\s+/ )
|
122
|
+
when "#"
|
123
|
+
start_group :comment, scan( /#[^\n\r]*/ )
|
124
|
+
when /[A-Z]/
|
125
|
+
start_group @selector ? :ident : :constant, scan( /\w+/ )
|
126
|
+
@allow_operator = true
|
127
|
+
when /[a-z_]/
|
128
|
+
word = scan( /\w+[?!]?/ )
|
129
|
+
if !@selector && KEYWORDS.include?( word )
|
130
|
+
start_group :keyword, word
|
131
|
+
@allow_operator = false
|
132
|
+
elsif
|
133
|
+
start_group :ident, word
|
134
|
+
@allow_operator = true
|
135
|
+
end
|
136
|
+
@selector = false
|
137
|
+
when /\d/
|
138
|
+
start_group :number,
|
139
|
+
scan( /[\d_]+(\.[\d_]+)?([eE][\d_]+)?/ )
|
140
|
+
@allow_operator = true
|
141
|
+
when '"'
|
142
|
+
scan_delimited_region :punct, :string, "", true
|
143
|
+
@allow_operator = true
|
144
|
+
when '/'
|
145
|
+
if @allow_operator
|
146
|
+
start_group :punct, scan(%r{/})
|
147
|
+
@allow_operator = false
|
148
|
+
else
|
149
|
+
scan_delimited_region :punct, :regex, "", true
|
150
|
+
@allow_operator = true
|
151
|
+
end
|
152
|
+
when "'"
|
153
|
+
scan_delimited_region :punct, :string, "", false
|
154
|
+
@allow_operator = true
|
155
|
+
when "."
|
156
|
+
dots = scan( /\.{1,3}/ )
|
157
|
+
start_group :punct, dots
|
158
|
+
@selector = ( dots.length == 1 )
|
159
|
+
when /[@]/
|
160
|
+
start_group :attribute, scan( /@{1,2}\w*/ )
|
161
|
+
@allow_operator = true
|
162
|
+
when /[$]/
|
163
|
+
start_group :global, scan(/\$/)
|
164
|
+
start_group :global, scan( /\w+|./ ) if check(/./)
|
165
|
+
@allow_operator = true
|
166
|
+
when /[-!?*\/+=<>(\[\{}:;,&|%]/
|
167
|
+
start_group :punct, scan(/./)
|
168
|
+
@allow_operator = false
|
169
|
+
when /[)\]]/
|
170
|
+
start_group :punct, scan(/./)
|
171
|
+
@allow_operator = true
|
172
|
+
else
|
173
|
+
# all else just falls through this, to prevent
|
174
|
+
# infinite loops...
|
175
|
+
append getch
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# Scan a delimited region of text. This handles the simple cases (strings
|
185
|
+
# delimited with quotes) as well as the more complex cases of %-strings
|
186
|
+
# and here-documents.
|
187
|
+
#
|
188
|
+
# * +delim_group+ is the group to use to classify the delimiters of the
|
189
|
+
# region
|
190
|
+
# * +inner_group+ is the group to use to classify the contents of the
|
191
|
+
# region
|
192
|
+
# * +starter+ is the text to use as the starting delimiter
|
193
|
+
# * +exprs+ is a boolean flag indicating whether the region is an
|
194
|
+
# interpolated string or not
|
195
|
+
# * +delim+ is the text to use as the delimiter of the region. If +nil+,
|
196
|
+
# the next character will be treated as the delimiter.
|
197
|
+
# * +heredoc+ is either +false+, meaning the region is not a heredoc, or
|
198
|
+
# <tt>:flush</tt> (meaning the delimiter must be flushed left), or
|
199
|
+
# <tt>:float</tt> (meaning the delimiter doens't have to be flush left).
|
200
|
+
def scan_delimited_region( delim_group, inner_group, starter, exprs,
|
201
|
+
delim=nil, heredoc=false )
|
202
|
+
# begin
|
203
|
+
if !delim
|
204
|
+
start_group delim_group, starter
|
205
|
+
delim = scan( /./ )
|
206
|
+
append delim
|
207
|
+
|
208
|
+
delim = case delim
|
209
|
+
when '{' then '}'
|
210
|
+
when '(' then ')'
|
211
|
+
when '[' then ']'
|
212
|
+
when '<' then '>'
|
213
|
+
else delim
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
start_region inner_group
|
218
|
+
|
219
|
+
items = "\\\\|"
|
220
|
+
if heredoc
|
221
|
+
items << "(^"
|
222
|
+
items << '\s*' if heredoc == :float
|
223
|
+
items << "#{Regexp.escape(delim)}\s*?)#{EOL}"
|
224
|
+
else
|
225
|
+
items << "#{Regexp.escape(delim)}"
|
226
|
+
end
|
227
|
+
items << "|#(\\$|@@?|\\{)" if exprs
|
228
|
+
items = Regexp.new( items )
|
229
|
+
|
230
|
+
loop do
|
231
|
+
p = pos
|
232
|
+
match = scan_until( items )
|
233
|
+
if match.nil?
|
234
|
+
start_group inner_group, scan_until( /\Z/ )
|
235
|
+
break
|
236
|
+
else
|
237
|
+
text = pre_match[p..-1]
|
238
|
+
start_group inner_group, text if text.length > 0
|
239
|
+
case matched.strip
|
240
|
+
when "\\"
|
241
|
+
unless exprs
|
242
|
+
case peek(1)
|
243
|
+
when "'"
|
244
|
+
scan(/./)
|
245
|
+
start_group :escape, "\\'"
|
246
|
+
when "\\"
|
247
|
+
scan(/./)
|
248
|
+
start_group :escape, "\\\\"
|
249
|
+
else
|
250
|
+
start_group inner_group, "\\"
|
251
|
+
end
|
252
|
+
else
|
253
|
+
start_group :escape, "\\"
|
254
|
+
c = getch
|
255
|
+
append c
|
256
|
+
case c
|
257
|
+
when 'x'
|
258
|
+
append scan( /[a-fA-F0-9]{1,2}/ )
|
259
|
+
when /[0-7]/
|
260
|
+
append scan( /[0-7]{0,2}/ )
|
261
|
+
end
|
262
|
+
end
|
263
|
+
when delim
|
264
|
+
end_region inner_group
|
265
|
+
start_group delim_group, matched
|
266
|
+
break
|
267
|
+
when /^#/
|
268
|
+
do_highlight = (option(:expressions) == :highlight)
|
269
|
+
start_region :expr if do_highlight
|
270
|
+
start_group :expr, matched
|
271
|
+
case matched[1]
|
272
|
+
when ?{
|
273
|
+
depth = 1
|
274
|
+
content = ""
|
275
|
+
while depth > 0
|
276
|
+
p = pos
|
277
|
+
c = scan_until( /[\{}]/ )
|
278
|
+
if c.nil?
|
279
|
+
content << scan_until( /\Z/ )
|
280
|
+
break
|
281
|
+
else
|
282
|
+
depth += ( matched == "{" ? 1 : -1 )
|
283
|
+
content << pre_match[p..-1]
|
284
|
+
content << matched if depth > 0
|
285
|
+
end
|
286
|
+
end
|
287
|
+
if do_highlight
|
288
|
+
subtokenize "ruby", content
|
289
|
+
start_group :expr, "}"
|
290
|
+
else
|
291
|
+
append content + "}"
|
292
|
+
end
|
293
|
+
when ?$, ?@
|
294
|
+
append scan( /\w+/ )
|
295
|
+
end
|
296
|
+
end_region :expr if do_highlight
|
297
|
+
else raise "unexpected match on #{matched}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Scan a heredoc beginning at the current position.
|
304
|
+
#
|
305
|
+
# * +float+ indicates whether the delimiter may be floated to the right
|
306
|
+
# * +type+ is +nil+, a single quote, or a double quote
|
307
|
+
# * +delim+ is the delimiter to look for
|
308
|
+
def scan_heredoc(float, type, delim)
|
309
|
+
scan_delimited_region( :constant, :string, "", type != "'",
|
310
|
+
delim, float ? :float : :flush )
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
SYNTAX["ruby"] = Ruby
|
315
|
+
|
316
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# syntax highlighting
|
2
|
+
|
3
|
+
require_relative 'common'
|
4
|
+
|
5
|
+
module HH::Markup
|
6
|
+
|
7
|
+
TOKENIZER = HH::Syntax.load "ruby"
|
8
|
+
COLORS = {
|
9
|
+
:comment => {:stroke => "#887"},
|
10
|
+
:keyword => {:stroke => "#111"},
|
11
|
+
:method => {:stroke => "#C09", :weight => "bold"},
|
12
|
+
# :class => {:stroke => "#0c4", :weight => "bold"},
|
13
|
+
# :module => {:stroke => "#050"},
|
14
|
+
# :punct => {:stroke => "#668", :weight => "bold"},
|
15
|
+
:symbol => {:stroke => "#C30"},
|
16
|
+
:string => {:stroke => "#C90"},
|
17
|
+
:number => {:stroke => "#396" },
|
18
|
+
:regex => {:stroke => "#000", :fill => "#FFC" },
|
19
|
+
# :char => {:stroke => "#f07"},
|
20
|
+
:attribute => {:stroke => "#369" },
|
21
|
+
# :global => {:stroke => "#7FB" },
|
22
|
+
:expr => {:stroke => "#722" },
|
23
|
+
# :escape => {:stroke => "#277" }
|
24
|
+
:ident => {:stroke => "#994c99"},
|
25
|
+
:constant => {:stroke => "#630", :weight => "bold"},
|
26
|
+
:class => {:stroke => "#630", :weight => "bold"},
|
27
|
+
:matching => {:stroke => "#ff0", :weight => "bold"},
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
def highlight str, pos=nil, colors=COLORS
|
32
|
+
tokens = []
|
33
|
+
TOKENIZER.tokenize(str) do |t|
|
34
|
+
if t.group == :punct
|
35
|
+
# split punctuation into single characters tokens
|
36
|
+
# TODO: to it in the parser
|
37
|
+
tokens += t.split('').map{|s| HH::Syntax::Token.new(s, :punct)}
|
38
|
+
else
|
39
|
+
# add token as is
|
40
|
+
tokens << t
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
res = []
|
45
|
+
tokens.each do |token|
|
46
|
+
res <<
|
47
|
+
if colors[token.group]
|
48
|
+
#span(token, colors[token.group])
|
49
|
+
tmp = fg(token, tr_color(colors[token.group][:stroke]))
|
50
|
+
colors[token.group][:fill] ? bg(tmp, tr_color(colors[token.group][:fill])) : tmp
|
51
|
+
elsif colors[:any]
|
52
|
+
#span(token, colors[:any])
|
53
|
+
tmp = fg(token, tr_color(colors[:any][:stroke]))
|
54
|
+
colors[:any][:fill] ? bg(tmp, tr_color(colors[:any][:fill])) : tmp
|
55
|
+
else
|
56
|
+
token
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if pos.nil? or pos < 0
|
61
|
+
return res.join
|
62
|
+
end
|
63
|
+
|
64
|
+
token_index, matching_index = matching_token(tokens, pos)
|
65
|
+
|
66
|
+
if token_index
|
67
|
+
#res[token_index] = span(tokens[token_index], colors[:matching])
|
68
|
+
tmp = fg(tokens[token_index], tr_color(colors[:matching][:stroke]))
|
69
|
+
res[token_index] = colors[:matching][:fill] ? bg(tmp, tr_color(colors[:matching][:fill])) : tmp
|
70
|
+
if matching_index
|
71
|
+
#res[matching_index] = span(tokens[matching_index], colors[:matching])
|
72
|
+
tmp = fg(tokens[matching_index], tr_color(colors[:matching][:stroke]))
|
73
|
+
res[matching_index] = colors[:matching][:fill] ? bg(tmp, tr_color(colors[:matching][:fill])) : tmp
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
res.join
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def matching_token(tokens, pos)
|
82
|
+
curr_pos = 0
|
83
|
+
token_index = nil
|
84
|
+
tokens.each_with_index do |t, i|
|
85
|
+
curr_pos += t.size
|
86
|
+
if token_index.nil? and curr_pos >= pos
|
87
|
+
token_index = i
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if token_index.nil? then return nil end
|
92
|
+
|
93
|
+
match = matching_token_at_index(tokens, token_index);
|
94
|
+
if match.nil? and curr_pos == pos and token_index < tokens.size-1
|
95
|
+
# try the token before the cursor, instead of the one after
|
96
|
+
token_index += 1
|
97
|
+
match = matching_token_at_index(tokens, token_index)
|
98
|
+
end
|
99
|
+
|
100
|
+
if match
|
101
|
+
[token_index, match]
|
102
|
+
else
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def matching_token_at_index(tokens, index)
|
109
|
+
starts, ends, direction = matching_tokens(tokens, index)
|
110
|
+
if starts.nil?
|
111
|
+
return nil
|
112
|
+
end
|
113
|
+
|
114
|
+
stack_level = 1
|
115
|
+
index += direction
|
116
|
+
while index >= 0 and index < tokens.size
|
117
|
+
# TODO separate space in the tokenizer
|
118
|
+
t = tokens[index].gsub(/\s/, '')
|
119
|
+
if ends.include?(t) and not as_modifier?(tokens, index)
|
120
|
+
stack_level -= 1
|
121
|
+
return index if stack_level == 0
|
122
|
+
elsif starts.include?(t) and not as_modifier?(tokens, index)
|
123
|
+
stack_level += 1
|
124
|
+
end
|
125
|
+
index += direction
|
126
|
+
end
|
127
|
+
# no matching token found
|
128
|
+
return nil
|
129
|
+
end
|
130
|
+
|
131
|
+
# returns an array of tokens matching and the direction
|
132
|
+
def matching_tokens(tokens, index)
|
133
|
+
# TODO separate space in the tokenizer
|
134
|
+
token = tokens[index].gsub(/\s/, '')
|
135
|
+
starts = [token]
|
136
|
+
if OPEN_BRACKETS[token]
|
137
|
+
direction = 1
|
138
|
+
ends = [OPEN_BRACKETS[token]]
|
139
|
+
elsif CLOSE_BRACKETS[token]
|
140
|
+
direction = -1
|
141
|
+
ends = [CLOSE_BRACKETS[token]]
|
142
|
+
elsif OPEN_BLOCK.include?(token)
|
143
|
+
if as_modifier?(tokens, index)
|
144
|
+
return nil
|
145
|
+
end
|
146
|
+
direction = 1
|
147
|
+
ends = ['end']
|
148
|
+
starts = OPEN_BLOCK
|
149
|
+
elsif token == 'end'
|
150
|
+
direction = -1
|
151
|
+
ends = OPEN_BLOCK
|
152
|
+
else
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
[starts, ends, direction]
|
157
|
+
end
|
158
|
+
|
159
|
+
def as_modifier?(tokens, index)
|
160
|
+
|
161
|
+
if not MODIFIERS.include? tokens[index].gsub(/\s/, '')
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
|
165
|
+
index -= 1
|
166
|
+
# find last index before the token that is no space
|
167
|
+
index -= 1 while index >= 0 and tokens[index] =~ /\A[ \t]*\z/
|
168
|
+
|
169
|
+
if index < 0
|
170
|
+
# first character of the string
|
171
|
+
false
|
172
|
+
elsif tokens[index] =~ /\n[ \t]*\Z/
|
173
|
+
# first token of the line
|
174
|
+
false
|
175
|
+
elsif tokens[index].group == :punct
|
176
|
+
# preceded by a punctuation token on the same line
|
177
|
+
i = tokens[index].rindex(/\S/)
|
178
|
+
punc = tokens[index][i, 1]
|
179
|
+
# true if the preceeding statement was terminating
|
180
|
+
not NON_TERMINATING.include?(punc)
|
181
|
+
else
|
182
|
+
# preceded by a non punctuation token on the same line
|
183
|
+
true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
OPEN_BRACKETS = {
|
189
|
+
'{' => '}',
|
190
|
+
'(' => ')',
|
191
|
+
'[' => ']',
|
192
|
+
}
|
193
|
+
|
194
|
+
#close_bracket = {}
|
195
|
+
#OPEN_BRACKETS.each{|open, close| opens_bracket[close] = open}
|
196
|
+
#CLOSE_BRACKETS = opens_bracket
|
197
|
+
# the following is more readable :)
|
198
|
+
CLOSE_BRACKETS = {
|
199
|
+
'}' => '{',
|
200
|
+
')' => '(',
|
201
|
+
']' => '[',
|
202
|
+
}
|
203
|
+
|
204
|
+
BRACKETS = CLOSE_BRACKETS.keys + OPEN_BRACKETS.keys
|
205
|
+
|
206
|
+
OPEN_BLOCK = [
|
207
|
+
'def',
|
208
|
+
'class',
|
209
|
+
'module',
|
210
|
+
'do',
|
211
|
+
'if',
|
212
|
+
'unless',
|
213
|
+
'while',
|
214
|
+
'until',
|
215
|
+
'begin',
|
216
|
+
'for'
|
217
|
+
]
|
218
|
+
|
219
|
+
MODIFIERS = %w[if unless while until]
|
220
|
+
|
221
|
+
NON_TERMINATING = %w{+ - * / , . = ~ < > ( [}
|
222
|
+
end
|
data/lib/green_shoes.rb
CHANGED
@@ -48,3 +48,4 @@ autoload :ChipMunk, File.join(Shoes::DIR, 'ext/chipmunk')
|
|
48
48
|
autoload :Bloops, File.join(Shoes::DIR, 'ext/bloops')
|
49
49
|
autoload :Projector, File.join(Shoes::DIR, 'ext/projector')
|
50
50
|
autoload :Hpricot, File.join(Shoes::DIR, 'ext/hpricot')
|
51
|
+
autoload :HH, File.join(Shoes::DIR, 'ext/highlighter')
|
data/lib/shoes/basic.rb
CHANGED
data/lib/shoes/help.rb
CHANGED
@@ -3,6 +3,7 @@ class Manual < Shoes
|
|
3
3
|
url '/manual/(\d+)', :index
|
4
4
|
|
5
5
|
include Hpricot
|
6
|
+
include HH::Markup
|
6
7
|
|
7
8
|
def index pnum = 0
|
8
9
|
font LANG == 'ja' ? 'MS UI Gothic' : 'Arial'
|
@@ -84,7 +85,11 @@ class Manual < Shoes
|
|
84
85
|
flow do
|
85
86
|
background rgb(190, 190, 190), curve: 5
|
86
87
|
inscription link(fg('Run this', green)){eval mk_executable(_code), TOPLEVEL_BINDING}, margin_left: 480
|
87
|
-
|
88
|
+
if _code.include? 'te-su-to'
|
89
|
+
para fg(code(' ' + _code), maroon), NL, margin_left: -10
|
90
|
+
else
|
91
|
+
para code(highlight(' ' + _code, nil)), NL, margin_left: -10
|
92
|
+
end
|
88
93
|
end
|
89
94
|
para
|
90
95
|
end
|
@@ -329,7 +334,7 @@ class Manual < Shoes
|
|
329
334
|
|
330
335
|
def mk_sidebar_list num
|
331
336
|
toc = []
|
332
|
-
[0..3, 4..9, 10..16, 17..32, 33..
|
337
|
+
[0..3, 4..9, 10..16, 17..32, 33..36].each do |r|
|
333
338
|
toc.push TOC_LIST[r.first][0]
|
334
339
|
toc.push(TOC_LIST[r.first+1..r.last].to_a.map &:first) if r.include?(num)
|
335
340
|
end
|
data/lib/shoes/helper_methods.rb
CHANGED
@@ -194,7 +194,7 @@ class Shoes
|
|
194
194
|
mask_control app
|
195
195
|
repaint_all_by_order app
|
196
196
|
repaint_textcursors app
|
197
|
-
app.canvas.set_size 0, scrollable_height unless app.
|
197
|
+
app.canvas.set_size 0, scrollable_height unless(app.prjct or app.trvw)
|
198
198
|
true
|
199
199
|
end
|
200
200
|
|
@@ -284,6 +284,7 @@ class Shoes
|
|
284
284
|
def self.mouse_on? e
|
285
285
|
if e.is_a? Slot
|
286
286
|
mouse_x, mouse_y = e.app.win.pointer
|
287
|
+
mouse_y += e.app.scroll_top
|
287
288
|
(e.left..e.left+e.width).include?(mouse_x) and (e.top..e.top+e.height).include?(mouse_y)
|
288
289
|
else
|
289
290
|
mouse_x, mouse_y = e.real.pointer
|
data/lib/shoes/main.rb
CHANGED
@@ -12,7 +12,8 @@ class Shoes
|
|
12
12
|
args[:left] ||= 0
|
13
13
|
args[:top] ||= 0
|
14
14
|
projector = args[:prjct] = args[:projector]
|
15
|
-
args
|
15
|
+
treeview = args[:trvw] = args[:treeview]
|
16
|
+
[:projector, :treeview].each{|x| args.delete x}
|
16
17
|
|
17
18
|
app = App.new args
|
18
19
|
@apps.push app
|
@@ -62,7 +63,11 @@ class Shoes
|
|
62
63
|
mouse_leave_control app
|
63
64
|
end
|
64
65
|
|
65
|
-
app.canvas =
|
66
|
+
app.canvas = if treeview
|
67
|
+
Gtk::TreeView.new
|
68
|
+
else
|
69
|
+
projector ? Gtk::DrawingArea.new : Gtk::Layout.new
|
70
|
+
end
|
66
71
|
swin = Gtk::ScrolledWindow.new
|
67
72
|
swin.set_policy Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC
|
68
73
|
swin.vadjustment.step_increment = 10
|
data/lib/shoes/ruby.rb
CHANGED
data/lib/shoes/slot.rb
CHANGED
@@ -74,9 +74,8 @@ class Shoes
|
|
74
74
|
|
75
75
|
def fix_size; end
|
76
76
|
|
77
|
-
def clear &blk
|
78
|
-
@
|
79
|
-
@contents.each &:clear
|
77
|
+
def clear all = false, &blk
|
78
|
+
all ? @contents.each(&:clear_all) : @contents.each(&:clear)
|
80
79
|
@contents.each{|e| @app.mlcs.delete e; @app.mhcs.delete e}
|
81
80
|
@contents = []
|
82
81
|
if blk
|
@@ -91,6 +90,11 @@ class Shoes
|
|
91
90
|
end
|
92
91
|
end
|
93
92
|
|
93
|
+
def clear_all &blk
|
94
|
+
@app.delete_mouse_events self
|
95
|
+
clear true, &blk
|
96
|
+
end
|
97
|
+
|
94
98
|
def append &blk
|
95
99
|
prepend contents.length, &blk
|
96
100
|
end
|
data/samples/sample52.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require '../lib/green_shoes'
|
2
|
+
|
3
|
+
Shoes.app do
|
4
|
+
extend HH::Markup
|
5
|
+
colors = {
|
6
|
+
:comment => {:stroke => "#bba"},
|
7
|
+
:keyword => {:stroke => "#FCF91F"},
|
8
|
+
:method => {:stroke => "#C09"},
|
9
|
+
:symbol => {:stroke => "#9DF3C6"},
|
10
|
+
:string => {:stroke => "#C9F5A5"},
|
11
|
+
:number => {:stroke => "#C9F5A5"},
|
12
|
+
:regex => {:stroke => "#000", :fill => "#FFC" },
|
13
|
+
:attribute => {:stroke => "#C9F5A5"},
|
14
|
+
:expr => {:stroke => "#f33" },
|
15
|
+
:ident => {:stroke => "#6e7"},
|
16
|
+
:any => {:stroke => "#FFF"},
|
17
|
+
:constant => {:stroke => "#55f"},
|
18
|
+
:class => {:stroke => "#55f"},
|
19
|
+
:matching => {:stroke => "#f00"},
|
20
|
+
}
|
21
|
+
code = IO.read(ask_open_file)
|
22
|
+
button 'change color' do
|
23
|
+
@slot.clear do
|
24
|
+
background gray 0.1
|
25
|
+
para highlight code, nil, colors
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@slot = stack do
|
29
|
+
background gainsboro
|
30
|
+
para highlight code, nil
|
31
|
+
end
|
32
|
+
end
|
data/samples/sample53.rb
ADDED
data/samples/treeview.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# The following code was quoted from http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-gtk2-treev-trees
|
2
|
+
# and edited a little bit for Green Shoes
|
3
|
+
|
4
|
+
def setup_tree_view(treeview)
|
5
|
+
renderer = Gtk::CellRendererText.new
|
6
|
+
column = Gtk::TreeViewColumn.new("Buy", renderer, "text" => $buy_index)
|
7
|
+
treeview.append_column(column)
|
8
|
+
renderer = Gtk::CellRendererText.new
|
9
|
+
column = Gtk::TreeViewColumn.new("Count", renderer, "text" => $qty_index)
|
10
|
+
treeview.append_column(column)
|
11
|
+
renderer = Gtk::CellRendererText.new
|
12
|
+
column = Gtk::TreeViewColumn.new("Product", renderer, "text" => $prod_index)
|
13
|
+
treeview.append_column(column)
|
14
|
+
end
|
15
|
+
|
16
|
+
class GroceryItem
|
17
|
+
attr_accessor :product_type, :buy, :quantity, :product
|
18
|
+
def initialize(t,b,q,p)
|
19
|
+
@product_type, @buy, @quantity, @product = t, b, q, p
|
20
|
+
end
|
21
|
+
end
|
22
|
+
$buy_index = 0; $qty_index = 1; $prod_index = 2
|
23
|
+
$p_category = 0; $p_child = 1
|
24
|
+
|
25
|
+
list = Array.new
|
26
|
+
list[0] = GroceryItem.new($p_category, true, 0, "Cleaning Supplies")
|
27
|
+
list[1] = GroceryItem.new($p_child, true, 1, "Paper Towels")
|
28
|
+
list[2] = GroceryItem.new($p_child, true, 3, "Toilet Paper")
|
29
|
+
list[3] = GroceryItem.new($p_category, true, 0, "Food")
|
30
|
+
list[4] = GroceryItem.new($p_child, true, 2, "Bread")
|
31
|
+
list[5] = GroceryItem.new($p_child, false, 1, "Butter")
|
32
|
+
list[6] = GroceryItem.new($p_child, true, 1, "Milk")
|
33
|
+
list[7] = GroceryItem.new($p_child, false, 3, "Chips")
|
34
|
+
list[8] = GroceryItem.new($p_child, true, 4, "Soda")
|
35
|
+
|
36
|
+
setup_tree_view($app.canvas)
|
37
|
+
|
38
|
+
store = Gtk::TreeStore.new(TrueClass, Integer, String)
|
39
|
+
parent = child = nil
|
40
|
+
|
41
|
+
list.each_with_index do |e, i|
|
42
|
+
if (e.product_type == $p_category)
|
43
|
+
j = i + 1
|
44
|
+
while j < list.size && list[j].product_type != $p_category
|
45
|
+
list[i].quantity += list[j].quantity if list[j].buy
|
46
|
+
j += 1
|
47
|
+
end
|
48
|
+
parent = store.append(nil)
|
49
|
+
parent[$buy_index] = list[i].buy
|
50
|
+
parent[$qty_index] = list[i].quantity
|
51
|
+
parent[$prod_index] = list[i].product
|
52
|
+
else
|
53
|
+
child = store.append(parent)
|
54
|
+
child[$buy_index] = list[i].buy
|
55
|
+
child[$qty_index] = list[i].quantity
|
56
|
+
child[$prod_index] = list[i].product
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
$app.canvas.model = store
|
Binary file
|
Binary file
|
data/static/manual-en.txt
CHANGED
@@ -3877,3 +3877,34 @@ Hope this helps:
|
|
3877
3877
|
* You can join [[http://librelist.com/browser/shoes/ Shoes ML]] and feel free ask your questions.
|
3878
3878
|
* [[https://github.com/ashbb/green_shoes/ Current Source Code]] is on GitHub.
|
3879
3879
|
* Green Shoes Gem is on [[http://rubygems.org/gems/green_shoes RubyGems.org]].
|
3880
|
+
|
3881
|
+
== vs.RedShoes ==
|
3882
|
+
|
3883
|
+
Green Shoes is following Red Shoes, but not fully compatible.
|
3884
|
+
|
3885
|
+
==== TextBlock ====
|
3886
|
+
|
3887
|
+
The following two snippets are same in Red Shoes.
|
3888
|
+
|
3889
|
+
{{{
|
3890
|
+
Shoes.app do
|
3891
|
+
para 'hello ' * 20
|
3892
|
+
end
|
3893
|
+
}}}
|
3894
|
+
|
3895
|
+
{{{
|
3896
|
+
Shoes.app do
|
3897
|
+
20.times{para 'hello '}
|
3898
|
+
end
|
3899
|
+
}}}
|
3900
|
+
|
3901
|
+
But in Green Shoes, need to add `:width` size explicitly.
|
3902
|
+
|
3903
|
+
{{{
|
3904
|
+
Shoes.app do
|
3905
|
+
20.times{para 'hello ', width: 40}
|
3906
|
+
end
|
3907
|
+
}}}
|
3908
|
+
|
3909
|
+
If you don't specify the `:width` size, Green Shoes makes a TextBlock object with
|
3910
|
+
the `parent.width`.
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 233
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.233.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- ashbb
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-06-
|
17
|
+
date: 2011-06-14 00:00:00 +09:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -50,6 +50,10 @@ files:
|
|
50
50
|
- lib/ext/bloops/songs/simpsons_theme_song_by_why.rb
|
51
51
|
- lib/ext/chipmunk.rb
|
52
52
|
- lib/ext/chipmunk/chipmunk.so
|
53
|
+
- lib/ext/highlighter.rb
|
54
|
+
- lib/ext/highlighter/common.rb
|
55
|
+
- lib/ext/highlighter/lang/ruby.rb
|
56
|
+
- lib/ext/highlighter/markup.rb
|
53
57
|
- lib/ext/hpricot.rb
|
54
58
|
- lib/ext/hpricot/blankslate.rb
|
55
59
|
- lib/ext/hpricot/builder.rb
|
@@ -196,6 +200,8 @@ files:
|
|
196
200
|
- samples/sample5.rb
|
197
201
|
- samples/sample50.rb
|
198
202
|
- samples/sample51.rb
|
203
|
+
- samples/sample52.rb
|
204
|
+
- samples/sample53.rb
|
199
205
|
- samples/sample6.rb
|
200
206
|
- samples/sample7.rb
|
201
207
|
- samples/sample8.rb
|
@@ -203,6 +209,7 @@ files:
|
|
203
209
|
- samples/sample99.rb
|
204
210
|
- samples/shell.png
|
205
211
|
- samples/splash-hand.png
|
212
|
+
- samples/treeview.rb
|
206
213
|
- snapshots/helloworld.png
|
207
214
|
- snapshots/mini-hh.png
|
208
215
|
- snapshots/sample1.png
|
@@ -255,6 +262,8 @@ files:
|
|
255
262
|
- snapshots/sample5.png
|
256
263
|
- snapshots/sample50.png
|
257
264
|
- snapshots/sample51.png
|
265
|
+
- snapshots/sample52.png
|
266
|
+
- snapshots/sample53.png
|
258
267
|
- snapshots/sample6.png
|
259
268
|
- snapshots/sample7.png
|
260
269
|
- snapshots/sample8.png
|