Menu

[r2597]: / trunk / src / sample / RubyConsole / console.rb  Maximize  Restore  History

Download this file

313 lines (281 with data), 9.1 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# Copyright (c) 2007 The RubyCocoa Project.
# Copyright (c) 2006 Tim Burks, Neon Design Technology, Inc.
#
# Find more information about this file online at:
# http://www.rubycocoa.com/mastering-cocoa-with-ruby
require 'irb'
require 'osx/cocoa'
class ConsoleWindowController < OSX::NSObject
attr_accessor :window, :textview, :console
def initWithFrame(frame)
init
styleMask = OSX::NSTitledWindowMask + OSX::NSClosableWindowMask +
OSX::NSMiniaturizableWindowMask + OSX::NSResizableWindowMask
@window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
frame, styleMask, OSX::NSBackingStoreBuffered, false)
@textview = OSX::NSTextView.alloc.initWithFrame(frame)
@console = RubyConsole.alloc.initWithTextView @textview
with @window do |w|
w.contentView = scrollableView(@textview)
w.title = "RubyCocoa Console"
w.delegate = self
w.center
w.makeKeyAndOrderFront(self)
end
self
end
def run
@console.performSelector_withObject_afterDelay("run:", self, 0)
end
def windowWillClose(notification)
OSX::NSApplication.sharedApplication.terminate(self)
end
def windowShouldClose(notification)
@alert = OSX::NSAlert.alloc.init
with @alert do |a|
a.messageText = "Do you really want to close this console?\n"
+ "Your application will exit."
a.alertStyle = OSX::NSCriticalAlertStyle
a.addButtonWithTitle("OK")
a.addButtonWithTitle("Cancel")
a.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo(
window, self, "alertDidEnd:returnCode:contextInfo:", nil)
end
false
end
def alertDidEnd_returnCode_contextInfo(alert, code, contextInfo)
window.close if (code == OSX::NSAlertFirstButtonReturn)
end
end
def with(x)
yield x if block_given?; x
end if not defined? with
def scrollableView(content)
scrollview = OSX::NSScrollView.alloc.initWithFrame(content.frame)
clipview = OSX::NSClipView.alloc.initWithFrame(scrollview.frame)
scrollview.contentView = clipview
scrollview.documentView = clipview.documentView = content
content.frame = clipview.frame
scrollview.hasVerticalScroller = scrollview.hasHorizontalScroller =
scrollview.autohidesScrollers = true
resizingMask = OSX::NSViewWidthSizable + OSX::NSViewHeightSizable
content.autoresizingMask = clipview.autoresizingMask =
scrollview.autoresizingMask = resizingMask
scrollview
end
class RubyCocoaInputMethod < IRB::StdioInputMethod
def initialize(console)
IRB.init_config(nil)
super() # superclass method has no arguments
@console = console
@history_index = 1
@continued_from_line = nil
end
def gets
m = @prompt.match(/(\d+)[>*]/)
level = m ? m[1].to_i : 0
if level > 0
@continued_from_line ||= @line_no
elsif @continued_from_line
mergeLastNLines(@line_no - @continued_from_line + 1)
@continued_from_line = nil
end
@console.write @prompt+" "*level
string = @console.readLine
@line_no += 1
@history_index = @line_no + 1
@line[@line_no] = string
string
end
def mergeLastNLines(i)
return unless i > 1
range = -i..-1
@line[range] = @line[range].map {|l| l.chomp}.join("\n")
@line_no -= (i-1)
@history_index -= (i-1)
end
def prevCmd
return "" if @line_no == 0
@history_index -= 1 unless @history_index <= 1
@line[@history_index]
end
def nextCmd
return "" if (@line_no == 0) or (@history_index >= @line_no)
@history_index += 1
@line[@history_index]
end
end
# this is an output handler for IRB
# and a delegate and controller for an NSTextView
class RubyConsole < OSX::NSObject
attr_accessor :textview, :inputMethod
def initWithTextView(textview)
init
@textview = textview
@textview.delegate = self
@textview.richText = false
@textview.continuousSpellCheckingEnabled = false
@textview.font = @font = OSX::NSFont.fontWithName_size('Monaco', 18.0)
@inputMethod = RubyCocoaInputMethod.new(self)
@context = Kernel::binding
@startOfInput = 0
self
end
def run(sender = nil)
@textview.window.makeKeyAndOrderFront(self)
IRB.startInConsole(self)
OSX::NSApplication.sharedApplication.terminate(self)
end
def attString(string)
OSX::NSAttributedString.alloc.initWithString_attributes(
string, { OSX::NSFontAttributeName => @font })
end
def write(object)
string = object.to_s
@textview.textStorage.insertAttributedString_atIndex(
attString(string), @startOfInput)
@startOfInput += string.length
@textview.scrollRangeToVisible([lengthOfTextView, 0])
handleEvents if OSX::NSApplication.sharedApplication.isRunning
end
def moveAndScrollToIndex(index)
range = OSX::NSRange.new(index, 0)
@textview.scrollRangeToVisible(range)
@textview.setSelectedRange(range)
end
def lengthOfTextView
@textview.textStorage.mutableString.length
end
def currentLine
text = @textview.textStorage.mutableString
text.substringWithRange(
OSX::NSRange.new(@startOfInput, text.length - @startOfInput)).to_s
end
def readLine
app = OSX::NSApplication.sharedApplication
@startOfInput = lengthOfTextView
loop do
event = app.nextEventMatchingMask_untilDate_inMode_dequeue(
OSX::NSAnyEventMask,
OSX::NSDate.distantFuture(),
OSX::NSDefaultRunLoopMode,
true)
if (event.oc_type == OSX::NSKeyDown) and
event.window and
(event.window.isEqual? @textview.window)
break if event.characters.to_s == "\r"
if (event.modifierFlags & OSX::NSControlKeyMask) != 0
case event.keyCode
when 0
moveAndScrollToIndex(@startOfInput) # control-a
when 14
moveAndScrollToIndex(lengthOfTextView) # control-e
end
end
end
app.sendEvent(event)
end
lineToReturn = currentLine
@startOfInput = lengthOfTextView
write("\n")
return lineToReturn + "\n"
end
def handleEvents
app = OSX::NSApplication.sharedApplication
event = app.nextEventMatchingMask_untilDate_inMode_dequeue(
OSX::NSAnyEventMask,
OSX::NSDate.dateWithTimeIntervalSinceNow(0.01),
OSX::NSDefaultRunLoopMode,
true)
if event
if (event.oc_type == OSX::NSKeyDown) and
event.window and
(event.window.isEqualTo @textview.window) and
(event.charactersIgnoringModifiers.to_s == 'c') and
(event.modifierFlags & OSX::NSControlKeyMask)
raise IRB::Abort, "abort, then interrupt!!" # that's what IRB says...
else
app.sendEvent(event)
end
end
end
def replaceLineWithHistory(s)
range = OSX::NSRange.new(@startOfInput, lengthOfTextView - @startOfInput)
@textview.textStorage.replaceCharactersInRange_withAttributedString(
range, attString(s.chomp))
@textview.scrollRangeToVisible([lengthOfTextView, 0])
true
end
# delegate methods
def textView_shouldChangeTextInRange_replacementString(
textview, range, replacement)
return false if range.location < @startOfInput
replacement = replacement.to_s.gsub("\r","\n")
if replacement.length > 0 and replacement[-1].chr == "\n"
@textview.textStorage.appendAttributedString(
attString(replacement)
) if currentLine != ""
@startOfInput = lengthOfTextView
false # don't insert replacement text because we've already inserted it
else
true # caller should insert replacement text
end
end
def textView_willChangeSelectionFromCharacterRange_toCharacterRange(
textview, oldRange, newRange)
return oldRange if (newRange.length == 0) and
(newRange.location < @startOfInput)
newRange
end
def textView_doCommandBySelector(textview, selector)
case selector
when "moveUp:"
replaceLineWithHistory(@inputMethod.prevCmd)
when "moveDown:"
replaceLineWithHistory(@inputMethod.nextCmd)
else
false
end
end
end
module IRB
def IRB.startInConsole(console)
IRB.setup(nil)
@CONF[:PROMPT_MODE] = :SIMPLE
@CONF[:VERBOSE] = false
@CONF[:ECHO] = true
irb = Irb.new(nil, console.inputMethod)
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
@CONF[:MAIN_CONTEXT] = irb.context
trap("SIGINT") do
irb.signal_handle
end
old_stdout, old_stderr = $stdout, $stderr
$stdout = $stderr = console
catch(:IRB_EXIT) do
loop do
begin
irb.eval_input
rescue Exception
puts "Error: #{$!}"
end
end
end
$stdout, $stderr = old_stdout, old_stderr
end
class Context
def prompting?
true
end
end
end
class ApplicationDelegate < OSX::NSObject
def applicationDidFinishLaunching(sender)
$consoleWindowController = ConsoleWindowController.alloc.
initWithFrame([50,50,600,300])
$consoleWindowController.run
end
end
# set up the application delegate
$delegate = ApplicationDelegate.alloc.init
OSX::NSApplication.sharedApplication.setDelegate($delegate)