Hello community,

here is the log from the commit of package rubygem-web-console for 
openSUSE:Factory checked in at 2016-11-17 12:42:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-web-console (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-web-console.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-web-console"

Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-web-console/rubygem-web-console.changes  
2016-07-21 07:58:41.000000000 +0200
+++ 
/work/SRC/openSUSE:Factory/.rubygem-web-console.new/rubygem-web-console.changes 
    2016-11-17 12:43:00.000000000 +0100
@@ -1,0 +2,10 @@
+Sun Oct 30 05:46:40 UTC 2016 - [email protected]
+
+- updated to version 3.4.0
+ see installed CHANGELOG.markdown
+
+  ## 3.4.0
+  
+  * [#205](https://github.com/rails/web-console/pull/205) Introduce 
autocompletion ([@sh19910711])
+
+-------------------------------------------------------------------

Old:
----
  web-console-3.3.1.gem

New:
----
  web-console-3.4.0.gem

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rubygem-web-console.spec ++++++
--- /var/tmp/diff_new_pack.ikLIBW/_old  2016-11-17 12:43:01.000000000 +0100
+++ /var/tmp/diff_new_pack.ikLIBW/_new  2016-11-17 12:43:01.000000000 +0100
@@ -24,7 +24,7 @@
 #
 
 Name:           rubygem-web-console
-Version:        3.3.1
+Version:        3.4.0
 Release:        0
 %define mod_name web-console
 %define mod_full_name %{mod_name}-%{version}

++++++ web-console-3.3.1.gem -> web-console-3.4.0.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CHANGELOG.markdown new/CHANGELOG.markdown
--- old/CHANGELOG.markdown      2016-07-05 10:05:07.000000000 +0200
+++ new/CHANGELOG.markdown      2016-10-29 12:01:55.000000000 +0200
@@ -2,6 +2,10 @@
 
 ## master (unreleased)
 
+## 3.4.0
+
+* [#205](https://github.com/rails/web-console/pull/205) Introduce 
autocompletion ([@sh19910711])
+
 ## 3.3.1
 
 Drop support for Rails `4.2.0`.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/README.markdown new/README.markdown
--- old/README.markdown 2016-07-05 10:05:07.000000000 +0200
+++ new/README.markdown 2016-10-29 12:01:55.000000000 +0200
@@ -185,7 +185,7 @@
 
 ### Why I'm getting an undefined method `web_console`?
 
-Make sure you configuration lives in `config/environments/development.rb`.
+Make sure your configuration lives in `config/environments/development.rb`.
 
 ## Credits
 
Files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/context.rb 
new/lib/web_console/context.rb
--- old/lib/web_console/context.rb      1970-01-01 01:00:00.000000000 +0100
+++ new/lib/web_console/context.rb      2016-10-29 12:01:55.000000000 +0200
@@ -0,0 +1,43 @@
+module WebConsole
+  # A context lets you get object names related to the current session binding.
+  class Context
+    def initialize(binding)
+      @binding = binding
+    end
+
+    # Extracts entire objects which can be called by the current session unless
+    # the inputs is present.
+    #
+    # Otherwise, it extracts methods and constants of the object specified by
+    # the input.
+    def extract(input = nil)
+      input.present? ? local(input) : global
+    end
+
+    private
+
+      GLOBAL_OBJECTS = [
+        'instance_variables',
+        'local_variables',
+        'methods',
+        'class_variables',
+        'Object.constants',
+        'global_variables'
+      ]
+
+      def global
+        GLOBAL_OBJECTS.map { |cmd| eval(cmd) }
+      end
+
+      def local(input)
+        [
+          eval("#{input}.methods").map { |m| "#{input}.#{m}" },
+          eval("#{input}.constants").map { |c| "#{input}::#{c}" },
+        ]
+      end
+
+      def eval(cmd)
+        @binding.eval(cmd) rescue []
+      end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/locales/en.yml 
new/lib/web_console/locales/en.yml
--- old/lib/web_console/locales/en.yml  2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/locales/en.yml  2016-10-29 12:01:55.000000000 +0200
@@ -1,7 +1,7 @@
 en:
   errors:
     unavailable_session: |
-      Session %{id} is is no longer available in memory.
+      Session %{id} is no longer available in memory.
 
       If you happen to run on a multi-process server (like Unicorn or Puma) 
the process
       this request hit doesn't store %{id} in memory. Consider turning the 
number of
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/middleware.rb 
new/lib/web_console/middleware.rb
--- old/lib/web_console/middleware.rb   2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/middleware.rb   2016-10-29 12:01:55.000000000 +0200
@@ -103,7 +103,11 @@
 
       def update_repl_session(id, request)
         json_response_with_session(id, request) do |session|
-          { output: session.eval(request.params[:input]) }
+          if input = request.params[:input]
+            { output: session.eval(input) }
+          elsif input = request.params[:context]
+            { context: session.context(input) }
+          end
         end
       end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/railtie.rb 
new/lib/web_console/railtie.rb
--- old/lib/web_console/railtie.rb      2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/railtie.rb      2016-10-29 12:01:55.000000000 +0200
@@ -40,6 +40,9 @@
       if mount_point = config.web_console.mount_point
         Middleware.mount_point = mount_point.chomp('/')
       end
+      if root = Rails.application.config.relative_url_root
+        Middleware.mount_point = File.join(root, Middleware.mount_point)
+      end
     end
 
     initializer 'web_console.template_paths' do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/session.rb 
new/lib/web_console/session.rb
--- old/lib/web_console/session.rb      2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/session.rb      2016-10-29 12:01:55.000000000 +0200
@@ -43,7 +43,7 @@
     def initialize(bindings)
       @id = SecureRandom.hex(16)
       @bindings = bindings
-      @evaluator = Evaluator.new(bindings.first)
+      @evaluator = Evaluator.new(@current_binding = bindings.first)
 
       store_into_memory
     end
@@ -59,7 +59,12 @@
     #
     # Returns nothing.
     def switch_binding_to(index)
-      @evaluator = Evaluator.new(@bindings[index.to_i])
+      @evaluator = Evaluator.new(@current_binding = @bindings[index.to_i])
+    end
+
+    # Returns context of the current binding
+    def context(objpath)
+      Context.new(@current_binding).extract(objpath)
     end
 
     private
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/tasks/templates.rake 
new/lib/web_console/tasks/templates.rake
--- old/lib/web_console/tasks/templates.rake    1970-01-01 01:00:00.000000000 
+0100
+++ new/lib/web_console/tasks/templates.rake    2016-10-29 12:01:55.000000000 
+0200
@@ -0,0 +1,54 @@
+namespace :templates do
+  desc 'Run tests for templates'
+  task test: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
+  task serve: [ :npm, :rackup ]
+
+  workdir = Pathname(EXPANDED_CWD).join('test/templates')
+  pid     = Pathname(Dir.tmpdir).join("web_console_test.pid")
+  runner  = URI.parse("http://#{ENV['IP'] || '127.0.0.1'}:#{ENV['PORT'] || 
29292}/html/test_runner.html")
+  rackup  = "rackup --host #{runner.host} --port #{runner.port}"
+  result  = nil
+  browser = 'phantomjs'
+
+  def need_to_wait?(uri)
+    Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
+  rescue Errno::ECONNREFUSED
+    retry if yield
+  end
+
+  task :daemonize do
+    rackup += " -D --pid #{pid}"
+  end
+
+  task :npm => [ :phantomjs ] do
+    Dir.chdir(workdir) { system 'npm install --silent' }
+  end
+
+  task :phantomjs do
+    unless system("which #{browser} >/dev/null")
+      browser = './node_modules/.bin/phantomjs'
+      Dir.chdir(workdir) { system("test -f #{browser} || npm install --silent 
phantomjs-prebuilt") }
+    end
+  end
+
+  task :rackup do
+    Dir.chdir(workdir) { system rackup }
+  end
+
+  task :wait do
+    cnt = 0
+    need_to_wait?(runner) { sleep 1; cnt += 1; cnt < 5 }
+  end
+
+  task :mocha do
+    Dir.chdir(workdir) { result = system("#{browser} 
./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js #{runner} dot") }
+  end
+
+  task :kill do
+    system "kill #{File.read pid}"
+  end
+
+  task :exit do
+    exit result
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/tasks/test_templates.rake 
new/lib/web_console/tasks/test_templates.rake
--- old/lib/web_console/tasks/test_templates.rake       2016-07-05 
10:05:07.000000000 +0200
+++ new/lib/web_console/tasks/test_templates.rake       1970-01-01 
01:00:00.000000000 +0100
@@ -1,50 +0,0 @@
-namespace :test do
-  desc "Run tests for templates"
-  task templates: "templates:all"
-
-  namespace :templates do
-    task all: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
-    task serve: [ :npm, :rackup ]
-
-    work_dir    = Pathname(EXPANDED_CWD).join("test/templates")
-    pid_file    = 
Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid")
-    runner_uri  = URI.parse("http://localhost:29292/html/spec_runner.html";)
-    rackup_opts = "-p #{runner_uri.port}"
-    test_result = nil
-
-    def need_to_wait?(uri)
-      Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
-    rescue Errno::ECONNREFUSED
-      retry if yield
-    end
-
-    task :daemonize do
-      rackup_opts += " -D -P #{pid_file}"
-    end
-
-    task :npm do
-      Dir.chdir(work_dir) { system "npm install --silent" }
-    end
-
-    task :rackup do
-      Dir.chdir(work_dir) { system "bundle exec rackup #{rackup_opts}" }
-    end
-
-    task :wait do
-      cnt = 0
-      need_to_wait?(runner_uri) { sleep 1; cnt += 1; cnt < 5 }
-    end
-
-    task :mocha do
-      Dir.chdir(work_dir) { test_result = system("$(npm bin)/mocha-phantomjs 
#{runner_uri}") }
-    end
-
-    task :kill do
-      system "kill #{File.read pid_file}"
-    end
-
-    task :exit do
-      exit test_result
-    end
-  end
-end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/templates/console.js.erb 
new/lib/web_console/templates/console.js.erb
--- old/lib/web_console/templates/console.js.erb        2016-07-05 
10:05:07.000000000 +0200
+++ new/lib/web_console/templates/console.js.erb        2016-10-29 
12:01:55.000000000 +0200
@@ -44,6 +44,212 @@
   }
 }
 
+function Autocomplete(_words, prefix) {
+  this.words = prepareWords(_words);
+  this.current = -1;
+  this.left = 0; // [left, right)
+  this.right = this.words.length;
+  this.confirmed = false;
+
+  function createSpan(label, className) {
+    var el = document.createElement('span');
+    addClass(el, className);
+    el.innerText = label;
+    return el;
+  }
+
+  function prepareWords(words) {
+    // convert into an object with priority and element
+    var res = new Array(words.length);
+    for (var i = 0, ind = 0; i < words.length; ++i) {
+      res[i] = new Array(words[i].length);
+      for (var j = 0; j < words[i].length; ++j) {
+        res[i][j] = {
+          word: words[i][j],
+          priority: i,
+          element: createSpan(words[i][j], 'trimmed keyword')
+        };
+      }
+    }
+    // flatten and sort by alphabetical order to refine incrementally
+    res = flatten(res);
+    res.sort(function(a, b) { return a.word == b.word ? 0 : (a.word < b.word ? 
-1 : 1); });
+    for (var i = 0; i < res.length; ++i) res[i].element.dataset.index = i;
+    return res;
+  }
+
+  this.view = document.createElement('pre');
+  addClass(this.view, 'auto-complete console-message');
+  this.view.appendChild(this.prefix = createSpan('...', 'trimmed keyword'));
+  this.view.appendChild(this.stage = document.createElement('span'));
+  this.elements = this.stage.children;
+  this.view.appendChild(this.suffix = createSpan('...', 'trimmed keyword'));
+
+  this.refine(prefix || '');
+}
+
+Autocomplete.prototype.getSelectedWord = function() {
+  return this.lastSelected && this.lastSelected.innerText;
+};
+
+Autocomplete.prototype.onFinished = function(callback) {
+  this.onFinishedCallback = callback;
+  if (this.confirmed) callback(this.confirmed);
+};
+
+Autocomplete.prototype.onKeyDown = function(ev) {
+  var self = this;
+  if (!this.elements.length) return;
+
+  function move(nextCurrent) {
+    if (self.lastSelected) removeClass(self.lastSelected, 'selected');
+    addClass(self.lastSelected = self.elements[nextCurrent], 'selected');
+    self.trim(self.current, true);
+    self.trim(nextCurrent, false);
+    self.current = nextCurrent;
+  }
+
+  switch (ev.keyCode) {
+    case 69:
+      if (ev.ctrlKey) {
+        move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
+        return true;
+      }
+      return false;
+    case 9: // Tab
+      if (ev.shiftKey) { // move back
+        move(this.current - 1 < 0 ? this.elements.length - 1 : this.current - 
1);
+      } else { // move next
+        move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
+      }
+      return true;
+    case 13: // Enter
+      this.finish();
+      return true;
+    case 27: // Esc
+      this.cancel();
+      return true;
+    case 37: case 38: case 39: case 40: // disable using arrow keys on 
completing
+      return true;
+  }
+
+  return false;
+};
+
+Autocomplete.prototype.trim = function(from, needToTrim) {
+  var self = this;
+  var num = 5;
+
+  if (this.elements.length > num) {
+    (0 < from ? removeClass : addClass)(this.prefix, 'trimmed');
+    (from + num < this.elements.length ? removeClass : addClass)(this.suffix, 
'trimmed');
+  } else {
+    addClass(this.prefix, 'trimmed');
+    addClass(this.suffix, 'trimmed');
+  }
+
+  function iterate(x) {
+    for (var i = 0; i < num; ++i, ++x) if (0 <= x && x < self.elements.length) 
{
+      toggleClass(self.elements[x], 'trimmed');
+    }
+  }
+
+  var toggleClass = needToTrim ? addClass : removeClass;
+  if (from < 0) {
+    iterate(0);
+  } else if (from + num - 1 >= this.elements.length) {
+    iterate(this.elements.length - num);
+  } else {
+    iterate(from);
+  }
+};
+
+Autocomplete.prototype.refine = function(prefix) {
+  if (this.confirmed) return;
+  var inc = !this.prev || (prefix.length >= this.prev.length);
+  this.prev = prefix;
+  var self = this;
+
+  function remove(parent, child) {
+    if (parent == child.parentNode) parent.removeChild(child);
+  }
+
+  function toggle(el) {
+    return inc ? remove(self.stage, el) : self.stage.appendChild(el);
+  }
+
+  function startsWith(str, prefix) {
+    return !prefix || str.substr(0, prefix.length) === prefix;
+  }
+
+  function moveRight(l, r) {
+    while (l < r && inc !== startsWith(self.words[l].word, prefix)) 
toggle(self.words[l++].element);
+    return l;
+  }
+
+  function moveLeft(l, r) {
+    while (l < r - 1 && inc !== startsWith(self.words[r-1].word, prefix)) 
toggle(self.words[--r].element);
+    return r;
+  }
+
+  self.trim(self.current, true); // reset trimming
+
+  // Refine the range of words having same prefix
+  if (inc) {
+    self.left = moveRight(self.left, self.right);
+    self.right = moveLeft(self.left, self.right);
+  } else {
+    self.left = moveLeft(-1, self.left);
+    self.right = moveRight(self.right, self.words.length);
+  }
+
+  // Render elements with sorting by scope groups
+  var words = this.words.slice(this.left, this.right);
+  words.sort(function(a, b) { return a.priority == b.priority ? (a.word < 
b.word ? -1 : 1) : (a.priority < b.priority ? -1 : 1); });
+  removeAllChildren(this.elements);
+  for (var i = 0; i < words.length; ++i) {
+    this.stage.appendChild(words[i].element);
+  }
+
+  // Keep a previous selected element if the refined range includes the element
+  if (this.lastSelected && this.left <= this.lastSelected.dataset.index && 
this.lastSelected.dataset.index < this.right) {
+    this.current = Array.prototype.indexOf.call(this.elements, 
this.lastSelected);
+    this.trim(this.current, false);
+  } else {
+    if (this.lastSelected) removeClass(this.lastSelected, 'selected');
+    this.lastSelected = null;
+    this.current = -1;
+    this.trim(0, false);
+  }
+
+  if (self.left + 1 == self.right) {
+    self.current = 0;
+    self.finish();
+  } else if (self.left == self.right) {
+    self.cancel();
+  }
+};
+
+Autocomplete.prototype.finish = function() {
+  if (0 <= this.current && this.current < this.elements.length) {
+    this.confirmed = this.elements[this.current].innerText;
+    if (this.onFinishedCallback) this.onFinishedCallback(this.confirmed);
+    this.removeView();
+  } else {
+    this.cancel();
+  }
+};
+
+Autocomplete.prototype.cancel = function() {
+  if (this.onFinishedCallback) this.onFinishedCallback();
+  this.removeView();
+};
+
+Autocomplete.prototype.removeView = function() {
+  if (this.view.parentNode) this.view.parentNode.removeChild(this.view);
+  removeAllChildren(this.view);
+}
+
 // HTML strings for dynamic elements.
 var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup.html' 
%>;
 var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup.html' %>;
@@ -62,6 +268,7 @@
   this.prompt = getConfig('promptLabel', ' >>');
   this.mountPoint = getConfig('mountPoint');
   this.sessionId = getConfig('sessionId');
+  this.autocomplete = false;
 }
 
 REPLConsole.prototype.getSessionUrl = function(path) {
@@ -73,6 +280,16 @@
   return parts.join('/').replace(/([^:]\/)\/+/g, '$1');
 };
 
+REPLConsole.prototype.contextRequest = function(keyword, callback) {
+  putRequest(this.getSessionUrl(), 'context=' + getContext(keyword), 
function(xhr) {
+    if (xhr.status == 200) {
+      callback(null, JSON.parse(xhr.responseText));
+    } else {
+      callback(xhr.statusText);
+    }
+  });
+};
+
 REPLConsole.prototype.commandHandle = function(line, callback) {
   var self = this;
   var params = 'input=' + encodeURIComponent(line);
@@ -92,7 +309,7 @@
 
   function getErrorText(xhr) {
     if (!xhr.status) {
-      return "<%= t 'errors.connection_refused' %>";
+      return "Connection Refused";
     } else {
       return xhr.status + ' ' + xhr.statusText;
     }
@@ -199,6 +416,7 @@
   this.outer = consoleOuter;
   this.inner = findChild(this.outer, 'console-inner');
   this.clipboard = findChild(container, 'clipboard');
+  this.suggestWait = 1500;
   this.newPromptBox();
   this.insertCss();
 
@@ -267,10 +485,63 @@
   this.setInput(this._input, -1);
 };
 
+REPLConsole.prototype.getSuggestion = function(keyword) {
+  var self = this;
+
+  function show(found) {
+    if (!found) return;
+    var hint = self.promptDisplay.childNodes[1];
+    hint.className = 'console-hint';
+    hint.dataset.keyword = found;
+    hint.innerText = found.substr(self.suggestKeyword.length);
+    // clear hinting information after timeout in a few time 
+    if (self.suggestTimeout) clearTimeout(self.suggestTimeout);
+    self.suggestTimeout = setTimeout(function() { self.renderInput() }, 
self.suggestWait);
+  }
+
+  function find(context) {
+    var k = self.suggestKeyword;
+    for (var i = 0; i < context.length; ++i) if (context[i].substr(0, 
k.length) === k) {
+      if (context[i] === k) return;
+      return context[i];
+    }
+  }
+
+  function request(keyword, callback) {
+    self.contextRequest(keyword, function(err, res) {
+      if (err) throw new Error(err);
+      var c = flatten(res['context']);
+      c.sort();
+      callback(c);
+    });
+  }
+
+  self.suggestKeyword = keyword;
+  var input = getContext(keyword);
+  if (keyword.length - input.length < 3) return;
+
+  if (self.suggestInput !== input) {
+    self.suggestInput = input;
+    request(keyword, function(c) {
+      show(find(self.suggestContext = c));
+    });
+  } else if (self.suggestContext) {
+    show(find(self.suggestContext));
+  }
+};
+
+REPLConsole.prototype.getHintKeyword = function() {
+  var hint = this.promptDisplay.childNodes[1];
+  return hint.className === 'console-hint' && hint.dataset.keyword;
+};
+
 REPLConsole.prototype.setInput = function(input, caretPos) {
+  if (input == null) return; // keep value if input is undefined
   this._caretPos = caretPos === undefined ? input.length : caretPos;
   this._input = input;
+  if (this.autocomplete) this.autocomplete.refine(this.getCurrentWord());
   this.renderInput();
+  if (!this.autocomplete && input.length == this._caretPos) 
this.getSuggestion(this.getCurrentWord());
 };
 
 /**
@@ -292,9 +563,8 @@
   // Clear the current input.
   removeAllChildren(this.promptDisplay);
 
-  var promptCursor = document.createElement('span');
-  promptCursor.className = "console-cursor";
   var before, current, after;
+  var center = document.createElement('span');
 
   if (this._caretPos < 0) {
     before = this._input;
@@ -310,9 +580,12 @@
   }
 
   this.promptDisplay.appendChild(document.createTextNode(before));
-  promptCursor.appendChild(document.createTextNode(current));
-  this.promptDisplay.appendChild(promptCursor);
+  this.promptDisplay.appendChild(center);
   this.promptDisplay.appendChild(document.createTextNode(after));
+
+  var hint = this.autocomplete && this.autocomplete.getSelectedWord();
+  addClass(center, hint ? 'console-hint' : 'console-cursor');
+  center.appendChild(document.createTextNode(hint ? 
hint.substr(this.getCurrentWord().length) : current));
 };
 
 REPLConsole.prototype.writeOutput = function(output) {
@@ -340,6 +613,30 @@
   this.commandHandle(input);
 };
 
+REPLConsole.prototype.onTabKey = function() {
+  var self = this;
+
+  var hintKeyword;
+  if (hintKeyword = self.getHintKeyword()) {
+    self.swapCurrentWord(hintKeyword);
+    return;
+  }
+
+  if (self.autocomplete) return;
+  self.autocomplete = new Autocomplete([]);
+
+  self.contextRequest(self.getCurrentWord(), function(err, obj) {
+    if (err) return self.autocomplete = false;
+    self.autocomplete = new Autocomplete(obj['context'], 
self.getCurrentWord());
+    self.inner.appendChild(self.autocomplete.view);
+    self.autocomplete.onFinished(function(word) {
+      self.swapCurrentWord(word);
+      self.autocomplete = false;
+    });
+    self.scrollToBottom();
+  });
+};
+
 REPLConsole.prototype.onNavigateHistory = function(offset) {
   var command = this.commandStorage.navigate(offset) || "";
   this.setInput(command);
@@ -349,7 +646,26 @@
  * Handle control keys like up, down, left, right.
  */
 REPLConsole.prototype.onKeyDown = function(ev) {
+  if (this.autocomplete && this.autocomplete.onKeyDown(ev)) {
+    this.renderInput();
+    ev.preventDefault();
+    ev.stopPropagation();
+    return;
+  }
+
   switch (ev.keyCode) {
+    case 69:
+      // Ctrl-E
+      if (ev.ctrlKey) {
+        this.onTabKey();
+        ev.preventDefault();
+      }
+      break;
+    case 9:
+      // Tab
+      this.onTabKey();
+      ev.preventDefault();
+      break;
     case 13:
       // Enter key
       this.onEnterKey();
@@ -432,6 +748,11 @@
     var before = this._input.substring(0, caretPos);
     var after = this._input.substring(this._caretPos, this._input.length);
     this.setInput(before + after, caretPos);
+
+    if (!this._input) {
+      this.autocomplete && this.autocomplete.cancel();
+      this.autocomplete = false;
+    }
   }
 };
 
@@ -444,6 +765,31 @@
   this.setInput(before + char + after, this._caretPos + 1);
 };
 
+REPLConsole.prototype.swapCurrentWord = function(next) {
+  function right(s, pos) {
+    var x = s.indexOf(' ', pos);
+    return x === -1 ? s.length : x;
+  }
+
+  function swap(s, pos) {
+    return s.substr(0, s.lastIndexOf(' ', pos) + 1) + next + s.substr(right(s, 
pos))
+  }
+
+  if (!next) return;
+  var swapped = swap(this._input, this._caretPos);
+  this.setInput(swapped, this._caretPos + swapped.length - this._input.length);
+};
+
+REPLConsole.prototype.getCurrentWord = function() {
+  return (function(s, pos) {
+    var left = s.lastIndexOf(' ', pos);
+    if (left === -1) left = 0;
+    var right = s.indexOf(' ', pos)
+    if (right === -1) right = s.length - 1;
+    return s.substr(left, right - left + 1).replace(/^\s+|\s+$/g,'');
+  })(this._input, this._caretPos);
+};
+
 REPLConsole.prototype.scrollToBottom = function() {
   this.outer.scrollTop = this.outer.scrollHeight;
 };
@@ -514,7 +860,7 @@
     for (var i = 0; i < el.length; ++ i) {
       addClass(el[i], className);
     }
-  } else {
+  } else if (!hasClass(el, className)) {
     el.className += " " + className;
   }
 }
@@ -562,3 +908,15 @@
 } else {
   window.REPLConsole = REPLConsole;
 }
+
+// Split string by module operators of ruby
+function getContext(s) {
+  var methodOp = s.lastIndexOf('.');
+  var moduleOp = s.lastIndexOf('::');
+  var x = methodOp > moduleOp ? methodOp : moduleOp;
+  return x !== -1 ? s.substr(0, x) : '';
+}
+
+function flatten(arrays) {
+  return Array.prototype.concat.apply([], arrays);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/templates/style.css.erb 
new/lib/web_console/templates/style.css.erb
--- old/lib/web_console/templates/style.css.erb 2016-07-05 10:05:07.000000000 
+0200
+++ new/lib/web_console/templates/style.css.erb 2016-10-29 12:01:55.000000000 
+0200
@@ -11,6 +11,12 @@
 .console .console-prompt-box { color: #FFF; }
 .console .console-message { color: #1AD027; margin: 0; border: 0; white-space: 
pre-wrap; background-color: #333; padding: 0; }
 .console .console-message.error-message { color: #fc9; }
+.console .console-message.auto-complete { word-break: break-all; }
+.console .console-message.auto-complete .keyword { margin-right: 11px; }
+.console .console-message.auto-complete .keyword.selected { background: #FFF; 
color: #000; }
+.console .console-message.auto-complete .hidden { display: none; }
+.console .console-message.auto-complete .trimmed { display: none; }
+.console .console-hint { color: #096; }
 .console .console-focus .console-cursor { background: #FEFEFE; color: #333; 
font-weight: bold; }
 .console .resizer { background: #333; width: 100%; height: 4px; cursor: 
ns-resize; }
 .console .console-actions { padding-right: 3px; }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/testing/fake_middleware.rb 
new/lib/web_console/testing/fake_middleware.rb
--- old/lib/web_console/testing/fake_middleware.rb      2016-07-05 
10:05:07.000000000 +0200
+++ new/lib/web_console/testing/fake_middleware.rb      2016-10-29 
12:01:55.000000000 +0200
@@ -21,7 +21,7 @@
       end
 
       def view
-        @view ||= View.new(@view_path)
+        @view = View.new(@view_path)
       end
 
       private
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/version.rb 
new/lib/web_console/version.rb
--- old/lib/web_console/version.rb      2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/version.rb      2016-10-29 12:01:55.000000000 +0200
@@ -1,3 +1,3 @@
 module WebConsole
-  VERSION = '3.3.1'
+  VERSION = '3.4.0'
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console/view.rb new/lib/web_console/view.rb
--- old/lib/web_console/view.rb 2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console/view.rb 2016-10-29 12:01:55.000000000 +0200
@@ -31,7 +31,11 @@
     #
     # Helps to keep the Rails logs clean during errors.
     def render(*)
-      WebConsole.logger.silence { super }
+      if logger = WebConsole.logger and logger.respond_to?(:silence)
+        WebConsole.logger.silence { super }
+      else
+        super
+      end
     end
 
     # Override method for ActionView::Helpers::TranslationHelper#t.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/web_console.rb new/lib/web_console.rb
--- old/lib/web_console.rb      2016-07-05 10:05:07.000000000 +0200
+++ new/lib/web_console.rb      2016-10-29 12:01:55.000000000 +0200
@@ -15,6 +15,7 @@
   autoload :Whitelist
   autoload :Template
   autoload :Middleware
+  autoload :Context
 
   autoload_at 'web_console/errors' do
     autoload :Error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2016-07-05 10:05:07.000000000 +0200
+++ new/metadata        2016-10-29 12:01:55.000000000 +0200
@@ -1,7 +1,7 @@
 --- !ruby/object:Gem::Specification
 name: web-console
 version: !ruby/object:Gem::Version
-  version: 3.3.1
+  version: 3.4.0
 platform: ruby
 authors:
 - Charlie Somerville
@@ -11,7 +11,7 @@
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2016-07-05 00:00:00.000000000 Z
+date: 2016-10-29 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: railties
@@ -85,6 +85,7 @@
 - Rakefile
 - lib/web-console.rb
 - lib/web_console.rb
+- lib/web_console/context.rb
 - lib/web_console/errors.rb
 - lib/web_console/evaluator.rb
 - lib/web_console/exception_mapper.rb
@@ -99,7 +100,7 @@
 - lib/web_console/response.rb
 - lib/web_console/session.rb
 - lib/web_console/tasks/extensions.rake
-- lib/web_console/tasks/test_templates.rake
+- lib/web_console/tasks/templates.rake
 - lib/web_console/template.rb
 - lib/web_console/templates/_inner_console_markup.html.erb
 - lib/web_console/templates/_markup.html.erb


Reply via email to