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 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.227.0
1
+ 0.233.0
@@ -0,0 +1,2 @@
1
+ module ::HH end
2
+ require_relative 'highlighter/markup'
@@ -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
@@ -92,6 +92,8 @@ class Shoes
92
92
  end
93
93
  end
94
94
 
95
+ alias :clear_all :clear
96
+
95
97
  def positioning x, y, max
96
98
  if parent.is_a?(Flow) and x + @width <= parent.left + parent.width
97
99
  move3 x + parent.margin_left, max.top + parent.margin_top
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
- para fg(code(' ' + _code), maroon), NL, margin_left: -10
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..35].each do |r|
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
@@ -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.prjct
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.delete :projector
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 = projector ? Gtk::DrawingArea.new : Gtk::Layout.new
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
@@ -143,6 +143,10 @@ class Array
143
143
  def clear
144
144
  self.each &:clear
145
145
  end
146
+
147
+ def clear_all
148
+ self.each &:clear_all
149
+ end
146
150
  end
147
151
 
148
152
  class NilClass
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
- @app.delete_mouse_events self
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
@@ -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
@@ -0,0 +1,10 @@
1
+ require '../lib/green_shoes'
2
+
3
+ Shoes.app do
4
+ button 'Open TreeView Window' do
5
+ Shoes.app width: 275, height: 300, treeview: true do
6
+ $app = app
7
+ load './treeview.rb'
8
+ end
9
+ end
10
+ end
@@ -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
- - 227
7
+ - 233
8
8
  - 0
9
- version: 0.227.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-06 00:00:00 +09:00
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