web-console-rails3 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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);