web-console-rails3 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +2 -2
  3. data/app/assets/javascripts/web_console/application.js +1 -3
  4. data/app/assets/javascripts/web_console/console_sessions.js +3 -24
  5. data/app/assets/stylesheets/web_console/application.css +1 -0
  6. data/app/assets/stylesheets/web_console/console_sessions.css +0 -8
  7. data/app/controllers/web_console/application_controller.rb +1 -0
  8. data/app/controllers/web_console/console_sessions_controller.rb +24 -4
  9. data/app/models/web_console/console_session.rb +32 -62
  10. data/app/views/web_console/console_sessions/index.html.erb +9 -4
  11. data/config/routes.rb +7 -1
  12. data/lib/assets/javascripts/web-console.js +1 -0
  13. data/lib/assets/javascripts/web_console.js +202 -0
  14. data/lib/web_console.rb +1 -1
  15. data/lib/web_console/engine.rb +17 -7
  16. data/lib/web_console/slave.rb +112 -0
  17. data/lib/web_console/version.rb +1 -1
  18. data/test/controllers/web_console/console_sessions_controller_test.rb +29 -11
  19. data/test/dummy/config/application.rb +17 -1
  20. data/test/dummy/config/application.rb.orig +58 -0
  21. data/test/dummy/log/development.log +28962 -0
  22. data/test/dummy/log/test.log +329 -0
  23. data/test/dummy/tmp/cache/assets/CBD/6E0/sprockets%2F3293dfc0984076f0e8371a9e7640f4a8 +0 -0
  24. data/test/dummy/tmp/cache/assets/CD0/FC0/sprockets%2Fbb2777627d42a216f3d9ced4322040b3 +0 -0
  25. data/test/dummy/tmp/cache/assets/CFA/E30/sprockets%2F35be1d26565dc0310c29f1a5e2f62f10 +0 -0
  26. data/test/dummy/tmp/cache/assets/D1D/FD0/sprockets%2Fa4a5ffe670666ce3d8d59179905201ef +0 -0
  27. data/test/dummy/tmp/cache/assets/D21/6B0/sprockets%2F9e242803fe56d6305274ff7e6487deda +0 -0
  28. data/test/dummy/tmp/cache/assets/D22/980/sprockets%2Fce6aa94ad2bc107104c0540f62c5128c +0 -0
  29. data/test/dummy/tmp/cache/assets/D3E/380/sprockets%2F434d98c8380bb9daf43810155aaf68ba +0 -0
  30. data/test/dummy/tmp/cache/assets/D66/940/sprockets%2F6151175b6ad4f9bab0c7e2b508e7b70f +0 -0
  31. data/test/dummy/tmp/cache/assets/D69/710/sprockets%2Ff67078d4b979a58c97feede196f6b385 +0 -0
  32. data/test/dummy/tmp/cache/assets/D95/C40/sprockets%2F09cb0a274209abf0391cbfce6ee67b82 +0 -0
  33. data/test/dummy/tmp/cache/assets/D9B/A30/sprockets%2Fc3436b3fe5da7c2456f26e2ae36da6c5 +0 -0
  34. data/test/dummy/tmp/cache/assets/D9F/400/sprockets%2F7f60332f86073dc8ed80b4c2a9dfcbe1 +0 -0
  35. data/test/dummy/tmp/cache/assets/DAE/8C0/sprockets%2Fa9b8f7bc5ca2efe658a13d7f4609c729 +0 -0
  36. data/test/dummy/tmp/cache/assets/DB3/0C0/sprockets%2F689fb998e2a7add3e00db88df254c87a +0 -0
  37. data/test/dummy/tmp/cache/assets/DD4/440/sprockets%2Fa33d646ac00d8bc87a4a496af6eed96f +0 -0
  38. data/test/dummy/tmp/cache/assets/DDB/890/sprockets%2F5ed566ca9fafd1b82373ffea2a8d8681 +0 -0
  39. data/test/models/console_session_test.rb +16 -75
  40. data/test/web_console/slave_test.rb +53 -0
  41. data/test/web_console_test.rb +51 -11
  42. data/vendor/assets/javascripts/vt100.js +4408 -0
  43. data/vendor/assets/stylesheets/vt100.css +272 -0
  44. metadata +23 -42
  45. data/lib/web_console/fiber.rb +0 -48
  46. data/lib/web_console/repl.rb +0 -59
  47. data/lib/web_console/repl/dummy.rb +0 -38
  48. data/lib/web_console/repl/irb.rb +0 -62
  49. data/lib/web_console/stream.rb +0 -30
  50. data/test/web_console/repl/dummy_test.rb +0 -54
  51. data/test/web_console/repl/irb_test.rb +0 -156
  52. data/test/web_console/repl_test.rb +0 -15
  53. data/vendor/assets/javascripts/jquery.console.js +0 -727
@@ -1,59 +0,0 @@
1
- require 'active_support/core_ext/string/inflections'
2
-
3
- module WebConsole
4
- module REPL
5
- extend self
6
-
7
- # Registry of REPL implementations mapped to their correspondent adapter
8
- # classes.
9
- #
10
- # Don't manually alter the registry. Use WebConsole::REPL.register_adapter
11
- # for adding entries.
12
- def adapters
13
- @adapters ||= {}
14
- end
15
-
16
- # Register an adapter into the adapters registry.
17
- #
18
- # Registration maps and adapter class to an existing REPL implementation,
19
- # that we call an adaptee constant. If the adaptee constant is not given,
20
- # it is automatically derived from the adapter class name.
21
- #
22
- # For example, adapter named +WebConsole::REPL::IRB+ will derive the
23
- # adaptee constant to +::IRB+.
24
- #
25
- # If a block is given, it would be evaluated right after the adapter
26
- # registration.
27
- def register_adapter(adapter_class, adaptee_constant = nil, options = {})
28
- if adaptee_constant.is_a?(Hash)
29
- options = adaptee_constant
30
- adaptee_constant = nil
31
- end
32
- adaptee_constant = adapter_class if options[:standalone]
33
- adaptee_constant ||= derive_adaptee_constant_from(adapter_class)
34
- adapters[adaptee_constant] = adapter_class
35
- yield if block_given?
36
- end
37
-
38
- # Get the default adapter for the given application.
39
- #
40
- # By default the application will be Rails.application and the adapter
41
- # will be chosen from Rails.application.config.console.
42
- #
43
- # If no suitible adapter is found for the configured Rails console, a dummy
44
- # adapter will be used. You can evaluate code in it, but it won't support
45
- # any advanced features, like multiline code evaluation.
46
- def default(app = Rails.application)
47
- adapters[(app.config.console || ::IRB rescue ::IRB)] || adapters[Dummy]
48
- end
49
-
50
- private
51
- def derive_adaptee_constant_from(cls, suffix = 'REPL')
52
- "::#{cls.name.split('::').last.gsub(/#{suffix}$/i, '')}".constantize
53
- end
54
- end
55
- end
56
-
57
- # Require the builtin adapters.
58
- require 'web_console/repl/irb'
59
- require 'web_console/repl/dummy'
@@ -1,38 +0,0 @@
1
- require 'web_console/stream'
2
-
3
- module WebConsole
4
- module REPL
5
- # == Dummy\ Adapter
6
- #
7
- # Dummy adapter that is used as a fallback for REPL with no adapters.
8
- #
9
- # It provides only the most basic code evaluation with no multiline code
10
- # support.
11
- class Dummy
12
- def initialize(binding = TOPLEVEL_BINDING)
13
- @binding = binding
14
- end
15
-
16
- def prompt
17
- '>> '
18
- end
19
-
20
- def send_input(input)
21
- eval_result = nil
22
- streams_output = Stream.threadsafe_capture! do
23
- eval_result = @binding.eval(input).inspect
24
- end
25
- "#{streams_output}=> #{eval_result}\n"
26
- rescue Exception => exc
27
- exc.backtrace.join("\n")
28
- end
29
- end
30
-
31
- register_adapter Dummy, standalone: true do
32
- require 'rails/console/app'
33
- require 'rails/console/helpers'
34
-
35
- TOPLEVEL_BINDING.eval('self').send(:include, Rails::ConsoleMethods)
36
- end
37
- end
38
- end
@@ -1,62 +0,0 @@
1
- require 'irb'
2
- require 'web_console/fiber'
3
- require 'web_console/stream'
4
-
5
- module WebConsole
6
- module REPL
7
- # == IRB\ Adapter
8
- #
9
- # Adapter for the IRB REPL, which is the default Ruby on Rails console.
10
- class IRB
11
- # For some reason™ we have to be ::IRB::StdioInputMethod subclass to get
12
- # #prompt populated.
13
- #
14
- # Not a pretty OOP, but for now, we just have to deal with it.
15
- class FiberInputMethod < ::IRB::StdioInputMethod
16
- def initialize; end
17
-
18
- def gets
19
- @previous = Fiber.yield
20
- end
21
-
22
- def encoding
23
- (@previous || '').encoding
24
- end
25
- end
26
-
27
- def initialize(binding = TOPLEVEL_BINDING)
28
- initialize_irb_session!
29
- @input = FiberInputMethod.new
30
- @irb = ::IRB::Irb.new(::IRB::WorkSpace.new(binding), @input)
31
- @fiber = Fiber.new { @irb.eval_input }.tap(&:resume)
32
- finalize_irb_session!
33
- end
34
-
35
- def prompt
36
- @input.prompt
37
- end
38
-
39
- def send_input(input)
40
- Stream.threadsafe_capture! { @fiber.resume("#{input}\n") }
41
- end
42
-
43
- private
44
- def initialize_irb_session!(ap_path = nil)
45
- ::IRB.init_config(ap_path)
46
- ::IRB.run_config unless WebConsole.config.prevent_irbrc_execution
47
- end
48
-
49
- def finalize_irb_session!
50
- ::IRB.conf[:MAIN_CONTEXT] = @irb.context
51
- end
52
- end
53
-
54
- register_adapter IRB do
55
- require 'rails/console/app'
56
- require 'rails/console/helpers'
57
-
58
- # Include all of the rails console helpers in the IRB session.
59
- ::IRB::ExtendCommandBundle.send(:include, Rails::ConsoleMethods)
60
- end
61
- end
62
- end
@@ -1,30 +0,0 @@
1
- require 'mutex_m'
2
-
3
- module WebConsole
4
- module Stream
5
- extend Mutex_m
6
-
7
- def self.threadsafe_capture!(*streams)
8
- streams = [$stdout, $stderr] if streams.empty?
9
- synchronize do
10
- begin
11
- streams_copy = streams.collect(&:dup)
12
- replacement = Tempfile.new(name)
13
- streams.each do |stream|
14
- stream.reopen(replacement)
15
- stream.sync = true
16
- end
17
- yield
18
- streams.each(&:rewind)
19
- replacement.read
20
- ensure
21
- replacement.close
22
- replacement.unlink
23
- streams.each_with_index do |stream, i|
24
- stream.reopen(streams_copy[i])
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,54 +0,0 @@
1
- require 'test_helper'
2
-
3
- class REPLTest < ActiveSupport::TestCase
4
- setup do
5
- @dummy1 = @dummy = WebConsole::REPL::Dummy.new
6
- @dummy2 = WebConsole::REPL::Dummy.new
7
- end
8
-
9
- test 'sending input returns the result as output' do
10
- assert_equal "=> 42\n", @dummy.send_input('foo = 42')
11
- end
12
-
13
- test 'preserves the session in the binding' do
14
- assert_equal "=> 42\n", @dummy.send_input('foo = 42')
15
- assert_equal "=> 50\n", @dummy.send_input('foo + 8')
16
- end
17
-
18
- test 'session isolation requires own bindings' do
19
- dummy1 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
20
- dummy2 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
21
- assert_equal "=> 42\n", dummy1.send_input('foo = 42')
22
- assert_match %r{NameError}, dummy2.send_input('foo')
23
- end
24
-
25
- test 'session preservation requires same bindings' do
26
- assert_equal "=> 42\n", @dummy1.send_input('foo = 42')
27
- assert_equal "=> 42\n", @dummy2.send_input('foo')
28
- end
29
-
30
- test 'captures stdout output' do
31
- assert_equal "42\n=> nil\n", @dummy.send_input('puts 42')
32
- end
33
-
34
- test 'captures stderr output' do
35
- assert_equal "42\n=> 3\n", @dummy.send_input('$stderr.write("42\n")')
36
- end
37
-
38
- test 'prompt is present' do
39
- assert_not_nil @dummy.prompt
40
- end
41
-
42
- test 'rails helpers are available in the session' do
43
- each_rails_console_method do |meth|
44
- assert_equal "=> true\n", @dummy.send_input("respond_to? :#{meth}")
45
- end
46
- end
47
-
48
- private
49
- def each_rails_console_method(&block)
50
- require 'rails/console/app'
51
- require 'rails/console/helpers'
52
- Rails::ConsoleMethods.public_instance_methods.each(&block)
53
- end
54
- end
@@ -1,156 +0,0 @@
1
- require 'test_helper'
2
-
3
- class IRBTest < ActiveSupport::TestCase
4
- setup do
5
- @irb1 = @irb = WebConsole::REPL::IRB.new
6
- @irb2 = WebConsole::REPL::IRB.new
7
-
8
- # Flag to signalize that the .irbrc was read.
9
- $IRBRC_EXECUTED = false
10
-
11
- # Since IRB is kinda funky, it reads the .irbrc in $HOME/.irbrc earlier
12
- # that the one in the current working directory, we have to lie to it.
13
- @preserved_home, ENV['HOME'] = ENV['HOME'], nil
14
-
15
- # It also caches the procedure used to generate the .irbrc location.
16
- IRB.conf[:RC_NAME_GENERATOR] = nil
17
- end
18
-
19
- teardown do
20
- # Now, bring the working place as we have found it.
21
- ENV['HOME'] = @preserved_home
22
- WebConsole::Engine.config.web_console.prevent_irbrc_execution = false
23
- end
24
-
25
- test 'sending input returns the result as output' do
26
- assert_equal return_prompt(42), @irb.send_input('foo = 42')
27
- end
28
-
29
- test 'preserves the session in the binding' do
30
- assert_equal return_prompt(42), @irb.send_input('foo = 42')
31
- assert_equal return_prompt(50), @irb.send_input('foo + 8')
32
- end
33
-
34
- test 'session isolation requires own bindings' do
35
- irb1 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
36
- irb2 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
37
- assert_equal return_prompt(42), irb1.send_input('foo = 42')
38
- assert_match %r{NameError}, irb2.send_input('foo')
39
- end
40
-
41
- test 'session preservation requires same bindings' do
42
- assert_equal return_prompt(42), @irb1.send_input('foo = 42')
43
- assert_equal return_prompt(42), @irb2.send_input('foo')
44
- end
45
-
46
- test 'multiline sessions' do
47
- irb = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
48
- assert_equal "", irb.send_input('class A')
49
- assert_equal return_prompt('nil'), irb.send_input('end')
50
- assert_no_match %r{NameError}, irb.send_input('A')
51
- end
52
-
53
- test 'captures direct stdout output' do
54
- assert_equal "42\n#{return_prompt('nil')}", @irb.send_input('puts 42')
55
- end
56
-
57
- test 'captures direct stderr output' do
58
- assert_equal "42\n#{return_prompt(3)}", @irb.send_input('$stderr.write("42\n")')
59
- end
60
-
61
- test 'captures direct output from subprocesses' do
62
- assert_equal "42\n#{return_prompt(true)}", @irb.send_input('system "echo 42"')
63
- end
64
-
65
- test 'captures direct output from forks' do
66
- # This is a bummer, but currently I don't see how we can work around it,
67
- # without monkey patching fork and the crew to be blocking calls. This
68
- # won't scale well, but at least fork will show results. Otherwise, we can
69
- # document the behaviour and expect the user to wait themselves, if they
70
- # care about the output.
71
- assert_match %r{42\n}, @irb.send_input('Process.wait(fork { puts 42 })')
72
- end
73
-
74
- test 'multiline support between threads' do
75
- assert_equal "", @irb.send_input('class A')
76
- Thread.new do
77
- assert_equal return_prompt('nil'), @irb.send_input('end')
78
- assert_no_match %r{NameError}, @irb.send_input('A')
79
- end.join
80
- end
81
-
82
- test 'prompt is present' do
83
- assert_not_nil @irb.prompt
84
- end
85
-
86
- test 'prompt is determined by ::IRB.conf' do
87
- with_simple_prompt do
88
- assert '>> ', WebConsole::REPL::IRB.new.prompt
89
- end
90
- end
91
-
92
- test 'closest .irbrc is executed by default' do
93
- skip "I'm special. Travis is special. Who knows?"
94
- Dir.chdir(File.expand_path('../../../fixtures', __FILE__)) do
95
- WebConsole::REPL::IRB.new.tap { assert $IRBRC_EXECUTED }
96
- end
97
- end
98
-
99
- test 'closest .irbrc execution can be prevented' do
100
- WebConsole::Engine.config.web_console.prevent_irbrc_execution = true
101
- Dir.chdir(File.expand_path('../../../fixtures', __FILE__)) do
102
- WebConsole::REPL::IRB.new.tap { refute $IRBRC_EXECUTED }
103
- end
104
- end
105
-
106
- test 'rails helpers are available in the session' do
107
- each_rails_console_method do |meth|
108
- assert_equal return_prompt(true), @irb.send_input("respond_to? :#{meth}")
109
- end
110
- end
111
-
112
- private
113
- def currently_selected_prompt
114
- ::IRB.conf[:PROMPT][::IRB.conf[:PROMPT_MODE]]
115
- end
116
-
117
- def return_prompt(*args)
118
- sprintf(currently_selected_prompt[:RETURN], *args)
119
- end
120
-
121
- def input_prompt
122
- currently_selected_prompt[:PROMPT_I]
123
- end
124
-
125
- def with_simple_prompt
126
- previous_prompt = ::IRB.conf[:PROMPT]
127
- ::IRB.conf[:PROMPT] = :simple
128
- yield
129
- ensure
130
- ::IRB.conf[:PROMPT] = previous_prompt
131
- end
132
-
133
- def each_rails_console_method(&block)
134
- require 'rails/console/app'
135
- require 'rails/console/helpers'
136
- Rails::ConsoleMethods.public_instance_methods.each(&block)
137
- end
138
-
139
- def new_uninitialized_app(root = File.expand_path('../../../dummy', __FILE__))
140
- FileUtils.mkdir_p root
141
- Dir.chdir root
142
-
143
- old_app = Rails.application
144
- Rails.application = nil
145
-
146
- app = Class.new(Rails::Application)
147
- app.config.eager_load = false
148
- app.config.time_zone = 'UTC'
149
- app.config.middleware ||= Rails::Configuration::MiddlewareStackProxy.new
150
- app.config.active_support.deprecation = :notify
151
-
152
- yield app
153
- ensure
154
- Rails.application = old_app
155
- end
156
- end
@@ -1,15 +0,0 @@
1
- require 'test_helper'
2
-
3
- class REPLTest < ActiveSupport::TestCase
4
- test 'standalone adapter registration' do
5
- WebConsole::REPL::register_adapter adapter = Class.new, standalone: true
6
- assert_equal adapter, WebConsole::REPL::adapters[adapter]
7
- end
8
-
9
- test 'fallback for unsupported config.console' do
10
- app_mock = Class.new do
11
- define_singleton_method(:config) { OpenStruct.new(console: Class.new) }
12
- end
13
- assert_equal WebConsole::REPL::Dummy, WebConsole::REPL.default(app_mock)
14
- end
15
- end
@@ -1,727 +0,0 @@
1
- // JQuery Console 1.0
2
- // Sun Feb 21 20:28:47 GMT 2010
3
- //
4
- // Copyright 2010 Chris Done, Simon David Pratt. All rights reserved.
5
- //
6
- // Redistribution and use in source and binary forms, with or without
7
- // modification, are permitted provided that the following conditions
8
- // are met:
9
- //
10
- // 1. Redistributions of source code must retain the above
11
- // copyright notice, this list of conditions and the following
12
- // disclaimer.
13
- //
14
- // 2. Redistributions in binary form must reproduce the above
15
- // copyright notice, this list of conditions and the following
16
- // disclaimer in the documentation and/or other materials
17
- // provided with the distribution.
18
- //
19
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
- // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
- // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
- // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23
- // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24
- // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25
- // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
- // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
- // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
- // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
- // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
- // POSSIBILITY OF SUCH DAMAGE.
31
-
32
- // TESTED ON
33
- // Internet Explorer 6
34
- // Opera 10.01
35
- // Chromium 4.0.237.0 (Ubuntu build 31094)
36
- // Firefox 3.5.8, 3.6.2 (Mac)
37
- // Safari 4.0.5 (6531.22.7) (Mac)
38
- // Google Chrome 5.0.375.55 (Mac)
39
-
40
- (function($){
41
- var isWebkit = !!~navigator.userAgent.indexOf(' AppleWebKit/');
42
-
43
- $.fn.console = function(config){
44
- ////////////////////////////////////////////////////////////////////////
45
- // Constants
46
- // Some are enums, data types, others just for optimisation
47
- var keyCodes = {
48
- // left
49
- 37: moveBackward,
50
- // right
51
- 39: moveForward,
52
- // up
53
- 38: previousHistory,
54
- // down
55
- 40: nextHistory,
56
- // backspace
57
- 8: backDelete,
58
- // delete
59
- 46: forwardDelete,
60
- // end
61
- 35: moveToEnd,
62
- // start
63
- 36: moveToStart,
64
- // return
65
- 13: commandTrigger,
66
- // tab
67
- 18: doNothing,
68
- // tab
69
- 9: doComplete
70
- };
71
- var ctrlCodes = {
72
- // C-a
73
- 65: moveToStart,
74
- // C-e
75
- 69: moveToEnd,
76
- // C-d
77
- 68: forwardDelete,
78
- // C-n
79
- 78: nextHistory,
80
- // C-p
81
- 80: previousHistory,
82
- // C-b
83
- 66: moveBackward,
84
- // C-f
85
- 70: moveForward,
86
- // C-k
87
- 75: deleteUntilEnd
88
- };
89
- if(config.ctrlCodes) {
90
- $.extend(ctrlCodes, config.ctrlCodes);
91
- }
92
- var altCodes = {
93
- // M-f
94
- 70: moveToNextWord,
95
- // M-b
96
- 66: moveToPreviousWord,
97
- // M-d
98
- 68: deleteNextWord
99
- };
100
- var cursor = '<span class="jquery-console-cursor">&nbsp;</span>';
101
-
102
- ////////////////////////////////////////////////////////////////////////
103
- // Globals
104
- var container = $(this);
105
- var inner = $('<div class="jquery-console-inner"></div>');
106
- // erjiang: changed this from a text input to a textarea so we
107
- // can get pasted newlines
108
- var typer = $('<textarea class="jquery-console-typer"></textarea>');
109
- // Prompt
110
- var promptBox;
111
- var prompt;
112
- var promptLabel = config && config.promptLabel? config.promptLabel : "> ";
113
- var continuedPromptLabel = config && config.continuedPromptLabel?
114
- config.continuedPromptLabel : "> ";
115
- var column = 0;
116
- var promptText = '';
117
- var restoreText = '';
118
- var continuedText = '';
119
- // Prompt history stack
120
- var history = [];
121
- var ringn = 0;
122
- // For reasons unknown to The Sword of Michael himself, Opera
123
- // triggers and sends a key character when you hit various
124
- // keys like PgUp, End, etc. So there is no way of knowing
125
- // when a user has typed '#' or End. My solution is in the
126
- // typer.keydown and typer.keypress functions; I use the
127
- // variable below to ignore the keypress event if the keydown
128
- // event succeeds.
129
- var cancelKeyPress = 0;
130
- // When this value is false, the prompt will not respond to input
131
- var acceptInput = true;
132
- // When this value is true, the command has been canceled
133
- var cancelCommand = false;
134
-
135
- // External exports object
136
- var extern = {};
137
-
138
- ////////////////////////////////////////////////////////////////////////
139
- // Main entry point
140
- (function(){
141
- container.append(inner);
142
- inner.append(typer);
143
- typer.css({position:'absolute',top:0,left:'-9999px'});
144
- if (config.welcomeMessage)
145
- message(config.welcomeMessage,'jquery-console-welcome');
146
- newPromptBox();
147
- if (config.autofocus) {
148
- inner.addClass('jquery-console-focus');
149
- typer.focus();
150
- setTimeout(function(){
151
- inner.addClass('jquery-console-focus');
152
- typer.focus();
153
- },100);
154
- }
155
- extern.inner = inner;
156
- extern.typer = typer;
157
- extern.scrollToBottom = scrollToBottom;
158
- })();
159
-
160
- ////////////////////////////////////////////////////////////////////////
161
- // Reset terminal
162
- extern.reset = function(){
163
- var welcome = (typeof config.welcomeMessage != 'undefined');
164
- inner.parent().fadeOut(function(){
165
- inner.find('div').each(function(){
166
- if (!welcome) {
167
- $(this).remove();
168
- } else {
169
- welcome = false;
170
- }
171
- });
172
- newPromptBox();
173
- inner.parent().fadeIn(function(){
174
- inner.addClass('jquery-console-focus');
175
- typer.focus();
176
- });
177
- });
178
- };
179
-
180
- ////////////////////////////////////////////////////////////////////////
181
- // Reset terminal
182
- extern.notice = function(msg,style){
183
- var n = $('<div class="notice"></div>').append($('<div></div>').text(msg))
184
- .css({visibility:'hidden'});
185
- container.append(n);
186
- var focused = true;
187
- if (style=='fadeout')
188
- setTimeout(function(){
189
- n.fadeOut(function(){
190
- n.remove();
191
- });
192
- },4000);
193
- else if (style=='prompt') {
194
- var a = $('<br/><div class="action"><a href="javascript:">OK</a><div class="clear"></div></div>');
195
- n.append(a);
196
- focused = false;
197
- a.click(function(){ n.fadeOut(function(){ n.remove();inner.css({opacity:1}) }); });
198
- }
199
- var h = n.height();
200
- n.css({height:'0px',visibility:'visible'})
201
- .animate({height:h+'px'},function(){
202
- if (!focused) inner.css({opacity:0.5});
203
- });
204
- n.css('cursor','default');
205
- return n;
206
- };
207
-
208
- extern.promptLabel = function(text) {
209
- if (typeof text === 'string') {
210
- promptLabel = text;
211
- }
212
- };
213
-
214
- ////////////////////////////////////////////////////////////////////////
215
- // Make a new prompt box
216
- function newPromptBox() {
217
- column = 0;
218
- promptText = '';
219
- ringn = 0; // Reset the position of the history ring
220
- enableInput();
221
- promptBox = $('<div class="jquery-console-prompt-box"></div>');
222
- var label = $('<span class="jquery-console-prompt-label"></span>');
223
- var labelText = extern.continuedPrompt? continuedPromptLabel : promptLabel;
224
- promptBox.append(label.text(labelText).show());
225
- label.html(label.html().replace(' ','&nbsp;'));
226
- prompt = $('<span class="jquery-console-prompt"></span>');
227
- promptBox.append(prompt);
228
- inner.append(promptBox);
229
- updatePromptDisplay();
230
- };
231
-
232
- ////////////////////////////////////////////////////////////////////////
233
- // Handle setting focus
234
- container.click(function(){
235
- inner.addClass('jquery-console-focus');
236
- inner.removeClass('jquery-console-nofocus');
237
- if (isWebkit) {
238
- typer.focusWithoutScrolling();
239
- } else {
240
- typer.css('position', 'fixed').focus();
241
- }
242
- scrollToBottom();
243
- return false;
244
- });
245
-
246
- ////////////////////////////////////////////////////////////////////////
247
- // Handle losing focus
248
- typer.blur(function(){
249
- inner.removeClass('jquery-console-focus');
250
- inner.addClass('jquery-console-nofocus');
251
- });
252
-
253
- ////////////////////////////////////////////////////////////////////////
254
- // Bind to the paste event of the input box so we know when we
255
- // get pasted data
256
- typer.bind('paste', function(e) {
257
- // wipe typer input clean just in case
258
- typer.val("");
259
- // this timeout is required because the onpaste event is
260
- // fired *before* the text is actually pasted
261
- setTimeout(function() {
262
- typer.consoleInsert(typer.val());
263
- typer.val("");
264
- }, 0);
265
- });
266
-
267
- ////////////////////////////////////////////////////////////////////////
268
- // Handle key hit before translation
269
- // For picking up control characters like up/left/down/right
270
-
271
- typer.keydown(function(e){
272
- cancelKeyPress = 0;
273
- var keyCode = e.keyCode;
274
- // C-c: cancel the execution
275
- if(e.ctrlKey && keyCode == 67) {
276
- cancelKeyPress = keyCode;
277
- cancelExecution();
278
- return false;
279
- }
280
- if (acceptInput) {
281
- if (keyCode in keyCodes) {
282
- cancelKeyPress = keyCode;
283
- (keyCodes[keyCode])();
284
- return false;
285
- } else if (e.ctrlKey && keyCode in ctrlCodes) {
286
- cancelKeyPress = keyCode;
287
- (ctrlCodes[keyCode])();
288
- return false;
289
- } else if (e.altKey && keyCode in altCodes) {
290
- cancelKeyPress = keyCode;
291
- (altCodes[keyCode])();
292
- return false;
293
- }
294
- }
295
- });
296
-
297
- ////////////////////////////////////////////////////////////////////////
298
- // Handle key press
299
- typer.keypress(function(e){
300
- var keyCode = e.keyCode || e.which;
301
- if (isIgnorableKey(e)) {
302
- return false;
303
- }
304
- // C-v: don't insert on paste event
305
- if ((e.ctrlKey || e.metaKey) && String.fromCharCode(keyCode).toLowerCase() == 'v') {
306
- return true;
307
- }
308
- if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32){
309
- if (cancelKeyPress) return false;
310
- if (
311
- typeof config.charInsertTrigger == 'undefined' || (
312
- typeof config.charInsertTrigger == 'function' &&
313
- config.charInsertTrigger(keyCode,promptText)
314
- )
315
- ){
316
- typer.consoleInsert(keyCode);
317
- }
318
- }
319
- if (isWebkit) return false;
320
- });
321
-
322
- function isIgnorableKey(e) {
323
- // for now just filter alt+tab that we receive on some platforms when
324
- // user switches windows (goes away from the browser)
325
- return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey);
326
- };
327
-
328
- ////////////////////////////////////////////////////////////////////////
329
- // Rotate through the command history
330
- function rotateHistory(n){
331
- if (history.length == 0) return;
332
- ringn += n;
333
- if (ringn < 0) ringn = history.length;
334
- else if (ringn > history.length) ringn = 0;
335
- var prevText = promptText;
336
- if (ringn == 0) {
337
- promptText = restoreText;
338
- } else {
339
- promptText = history[ringn - 1];
340
- }
341
- if (config.historyPreserveColumn) {
342
- if (promptText.length < column + 1) {
343
- column = promptText.length;
344
- } else if (column == 0) {
345
- column = promptText.length;
346
- }
347
- } else {
348
- column = promptText.length;
349
- }
350
- updatePromptDisplay();
351
- };
352
-
353
- function previousHistory() {
354
- rotateHistory(-1);
355
- };
356
-
357
- function nextHistory() {
358
- rotateHistory(1);
359
- };
360
-
361
- // Add something to the history ring
362
- function addToHistory(line){
363
- history.push(line);
364
- restoreText = '';
365
- };
366
-
367
- // Delete the character at the current position
368
- function deleteCharAtPos(){
369
- if (column < promptText.length){
370
- promptText =
371
- promptText.substring(0,column) +
372
- promptText.substring(column+1);
373
- restoreText = promptText;
374
- return true;
375
- } else return false;
376
- };
377
-
378
- function backDelete() {
379
- if (moveColumn(-1)){
380
- deleteCharAtPos();
381
- updatePromptDisplay();
382
- }
383
- };
384
-
385
- function forwardDelete() {
386
- if (deleteCharAtPos()){
387
- updatePromptDisplay();
388
- }
389
- };
390
-
391
- function deleteUntilEnd() {
392
- while(deleteCharAtPos()) {
393
- updatePromptDisplay();
394
- }
395
- };
396
-
397
- function deleteNextWord() {
398
- // A word is defined within this context as a series of alphanumeric
399
- // characters.
400
- // Delete up to the next alphanumeric character
401
- while(
402
- column < promptText.length &&
403
- !isCharAlphanumeric(promptText[column])
404
- ) {
405
- deleteCharAtPos();
406
- updatePromptDisplay();
407
- }
408
- // Then, delete until the next non-alphanumeric character
409
- while(
410
- column < promptText.length &&
411
- isCharAlphanumeric(promptText[column])
412
- ) {
413
- deleteCharAtPos();
414
- updatePromptDisplay();
415
- }
416
- };
417
-
418
- ////////////////////////////////////////////////////////////////////////
419
- // Validate command and trigger it if valid, or show a validation error
420
- function commandTrigger() {
421
- var line = promptText;
422
- if (typeof config.commandValidate == 'function') {
423
- var ret = config.commandValidate(line);
424
- if (ret == true || ret == false) {
425
- if (ret) {
426
- handleCommand();
427
- }
428
- } else {
429
- commandResult(ret,"jquery-console-message-error");
430
- }
431
- } else {
432
- handleCommand();
433
- }
434
- };
435
-
436
- // Scroll to the bottom of the view
437
- function scrollToBottom() {
438
- var version = jQuery.fn.jquery.split('.');
439
- var major = parseInt(version[0]);
440
- var minor = parseInt(version[1]);
441
-
442
- // check if we're using jquery > 1.6
443
- if ((major == 1 && minor > 6) || major > 1) {
444
- inner.prop({ scrollTop: inner.prop("scrollHeight") });
445
- }
446
- else {
447
- inner.attr({ scrollTop: inner.attr("scrollHeight") });
448
- }
449
- };
450
-
451
- function cancelExecution() {
452
- if(typeof config.cancelHandle == 'function') {
453
- config.cancelHandle();
454
- }
455
- }
456
-
457
- ////////////////////////////////////////////////////////////////////////
458
- // Handle a command
459
- function handleCommand() {
460
- if (typeof config.commandHandle == 'function') {
461
- disableInput();
462
- addToHistory(promptText);
463
- var text = promptText;
464
- if (extern.continuedPrompt) {
465
- if (continuedText)
466
- continuedText += '\n' + promptText;
467
- else continuedText = promptText;
468
- } else continuedText = undefined;
469
- if (continuedText) text = continuedText;
470
- var ret = config.commandHandle(text,function(msgs){
471
- commandResult(msgs);
472
- });
473
- if (extern.continuedPrompt && !continuedText)
474
- continuedText = promptText;
475
- if (typeof ret == 'boolean') {
476
- if (ret) {
477
- // Command succeeded without a result.
478
- commandResult();
479
- } else {
480
- commandResult(
481
- 'Command failed.',
482
- "jquery-console-message-error"
483
- );
484
- }
485
- } else if (typeof ret == "string") {
486
- commandResult(ret,"jquery-console-message-success");
487
- } else if (typeof ret == 'object' && ret.length) {
488
- commandResult(ret);
489
- } else if (extern.continuedPrompt) {
490
- commandResult();
491
- }
492
- }
493
- };
494
-
495
- ////////////////////////////////////////////////////////////////////////
496
- // Disable input
497
- function disableInput() {
498
- acceptInput = false;
499
- };
500
-
501
- // Enable input
502
- function enableInput() {
503
- acceptInput = true;
504
- }
505
-
506
- ////////////////////////////////////////////////////////////////////////
507
- // Reset the prompt in invalid command
508
- function commandResult(msg,className) {
509
- column = -1;
510
- updatePromptDisplay();
511
- if (typeof msg == 'string') {
512
- message(msg,className);
513
- } else if ($.isArray(msg)) {
514
- for (var x in msg) {
515
- var ret = msg[x];
516
- message(ret.msg,ret.className);
517
- }
518
- } else { // Assume it's a DOM node or jQuery object.
519
- inner.append(msg);
520
- }
521
- newPromptBox();
522
- };
523
-
524
- ////////////////////////////////////////////////////////////////////////
525
- // Display a message
526
- function message(msg,className) {
527
- var mesg = $('<div class="jquery-console-message"></div>');
528
- if (className) mesg.addClass(className);
529
- mesg.filledText(msg).hide();
530
- inner.append(mesg);
531
- mesg.show();
532
- };
533
-
534
- ////////////////////////////////////////////////////////////////////////
535
- // Handle normal character insertion
536
- // data can either be a number, which will be interpreted as the
537
- // numeric value of a single character, or a string
538
- typer.consoleInsert = function(data){
539
- // TODO: remove redundant indirection
540
- var text = isNaN(data) ? data : String.fromCharCode(data);
541
- var before = promptText.substring(0,column);
542
- var after = promptText.substring(column);
543
- promptText = before + text + after;
544
- moveColumn(text.length);
545
- restoreText = promptText;
546
- updatePromptDisplay();
547
- };
548
-
549
- ////////////////////////////////////////////////////////////////////////
550
- // Move to another column relative to this one
551
- // Negative means go back, positive means go forward.
552
- function moveColumn(n){
553
- if (column + n >= 0 && column + n <= promptText.length){
554
- column += n;
555
- return true;
556
- } else return false;
557
- };
558
-
559
- function moveForward() {
560
- if(moveColumn(1)) {
561
- updatePromptDisplay();
562
- return true;
563
- }
564
- return false;
565
- };
566
-
567
- function moveBackward() {
568
- if(moveColumn(-1)) {
569
- updatePromptDisplay();
570
- return true;
571
- }
572
- return false;
573
- };
574
-
575
- function moveToStart() {
576
- if (moveColumn(-column))
577
- updatePromptDisplay();
578
- };
579
-
580
- function moveToEnd() {
581
- if (moveColumn(promptText.length-column))
582
- updatePromptDisplay();
583
- };
584
-
585
- function moveToNextWord() {
586
- while(
587
- column < promptText.length &&
588
- !isCharAlphanumeric(promptText[column]) &&
589
- moveForward()
590
- ) {}
591
- while(
592
- column < promptText.length &&
593
- isCharAlphanumeric(promptText[column]) &&
594
- moveForward()
595
- ) {}
596
- };
597
-
598
- function moveToPreviousWord() {
599
- // Move backward until we find the first alphanumeric
600
- while(
601
- column -1 >= 0 &&
602
- !isCharAlphanumeric(promptText[column-1]) &&
603
- moveBackward()
604
- ) {}
605
- // Move until we find the first non-alphanumeric
606
- while(
607
- column -1 >= 0 &&
608
- isCharAlphanumeric(promptText[column-1]) &&
609
- moveBackward()
610
- ) {}
611
- };
612
-
613
- function isCharAlphanumeric(charToTest) {
614
- if(typeof charToTest == 'string') {
615
- var code = charToTest.charCodeAt();
616
- return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) ||
617
- (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) ||
618
- (code >= '0'.charCodeAt() && code <= '9'.charCodeAt());
619
- }
620
- return false;
621
- };
622
-
623
- function doComplete() {
624
- if(typeof config.completeHandle == 'function') {
625
- var completions = config.completeHandle(promptText);
626
- var len = completions.length;
627
- if (len === 1) {
628
- extern.promptText(promptText + completions[0]);
629
- } else if (len > 1 && config.cols) {
630
- var prompt = promptText;
631
- // Compute the number of rows that will fit in the width
632
- var max = 0;
633
- for (var i = 0;i < len;i++) {
634
- max = Math.max(max, completions[i].length);
635
- }
636
- max += 2;
637
- var n = Math.floor(config.cols / max);
638
- var buffer = "";
639
- var col = 0;
640
- for (i = 0;i < len;i++) {
641
- var completion = completions[i];
642
- buffer += completions[i];
643
- for (var j = completion.length;j < max;j++) {
644
- buffer += " ";
645
- }
646
- if (++col >= n) {
647
- buffer += "\n";
648
- col = 0;
649
- }
650
- }
651
- commandResult(buffer,"jquery-console-message-value");
652
- extern.promptText(prompt);
653
- }
654
- }
655
- };
656
-
657
- function doNothing() {};
658
-
659
- extern.promptText = function(text){
660
- if (typeof text === 'string') {
661
- promptText = text;
662
- column = promptText.length;
663
- updatePromptDisplay();
664
- }
665
- return promptText;
666
- };
667
-
668
- ////////////////////////////////////////////////////////////////////////
669
- // Update the prompt display
670
- function updatePromptDisplay(){
671
- var line = promptText;
672
- var html = '';
673
- if (column > 0 && line == ''){
674
- // When we have an empty line just display a cursor.
675
- html = cursor;
676
- } else if (column == promptText.length){
677
- // We're at the end of the line, so we need to display
678
- // the text *and* cursor.
679
- html = htmlEncode(line) + cursor;
680
- } else {
681
- // Grab the current character, if there is one, and
682
- // make it the current cursor.
683
- var before = line.substring(0, column);
684
- var current = line.substring(column,column+1);
685
- if (current){
686
- current =
687
- '<span class="jquery-console-cursor">' +
688
- htmlEncode(current) +
689
- '</span>';
690
- }
691
- var after = line.substring(column+1);
692
- html = htmlEncode(before) + current + htmlEncode(after);
693
- }
694
- prompt.html(html);
695
- scrollToBottom();
696
- };
697
-
698
- // Simple HTML encoding
699
- // Simply replace '<', '>' and '&'
700
- // TODO: Use jQuery's .html() trick, or grab a proper, fast
701
- // HTML encoder.
702
- function htmlEncode(text){
703
- return (
704
- text.replace(/&/g,'&amp;')
705
- .replace(/</g,'&lt;')
706
- .replace(/</g,'&lt;')
707
- .replace(/ /g,'&nbsp;')
708
- .replace(/\n/g,'<br />')
709
- );
710
- };
711
-
712
- return extern;
713
- };
714
- // Simple utility for printing messages
715
- $.fn.filledText = function(txt){
716
- $(this).text(txt);
717
- $(this).html($(this).html().replace(/\n/g,'<br/>'));
718
- return this;
719
- };
720
-
721
- // Alternative method for focus without scrolling
722
- $.fn.focusWithoutScrolling = function(){
723
- var x = window.scrollX, y = window.scrollY;
724
- $(this).focus();
725
- window.scrollTo(x, y);
726
- };
727
- })(jQuery);