diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index e2b532e..f12f9d4 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -437,7 +437,7 @@ IPython.utils = (function (IPython) { return decodeURIComponent($('body').data(key)); }; - var absolute_cursor_pos = function (cm, cursor) { + var to_absolute_cursor_pos = function (cm, cursor) { // get the absolute cursor position from CodeMirror's col, ch if (!cursor) { cursor = cm.getCursor(); @@ -449,6 +449,27 @@ IPython.utils = (function (IPython) { return cursor_pos; }; + var from_absolute_cursor_pos = function (cm, cursor_pos) { + // turn absolute cursor postion into CodeMirror col, ch cursor + var i, line; + var offset = 0; + for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) { + if (offset + line.length < cursor_pos) { + offset += line.length + 1; + } else { + return { + line : i, + ch : cursor_pos - offset, + }; + } + } + // reached end, return endpoint + return { + ch : line.length - 1, + line : i - 1, + }; + }; + // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { @@ -457,7 +478,7 @@ IPython.utils = (function (IPython) { } var N= navigator.appName, ua= navigator.userAgent, tem; var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i); - if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1]; + if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1]; M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?']; return M; })(); @@ -523,7 +544,8 @@ IPython.utils = (function (IPython) { splitext : splitext, escape_html : escape_html, always_new : always_new, - absolute_cursor_pos : absolute_cursor_pos, + to_absolute_cursor_pos : to_absolute_cursor_pos, + from_absolute_cursor_pos : from_absolute_cursor_pos, browser : browser, platform: platform, is_or_has : is_or_has, diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index ac6d5f1..bddf732 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -11,15 +11,16 @@ var IPython = (function (IPython) { // easier key mapping var keycodes = IPython.keyboard.keycodes; + var utils = IPython.utils; - function prepend_n_prc(str, n) { + var prepend_n_prc = function(str, n) { for( var i =0 ; i< n ; i++){ str = '%'+str ; } return str; }; - function _existing_completion(item, completion_array){ + var _existing_completion = function(item, completion_array){ for( var i=0; i < completion_array.length; i++) { if (completion_array[i].trim().substr(-item.length) == item) { return true; @@ -147,13 +148,14 @@ var IPython = (function (IPython) { // one kernel completion came back, finish_completing will be called with the results // we fork here and directly call finish completing if kernel is busy + var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur); if (this.skip_kernel_completion) { - this.finish_completing({ + this.finish_completing({ content: { matches: [], - matched_text: "" - }); + cursor_start: cursor_pos, + cursor_end: cursor_pos, + }}); } else { - var cursor_pos = IPython.utils.absolute_cursor_pos(this.editor, cur); this.cell.kernel.complete(this.editor.getValue(), cursor_pos, $.proxy(this.finish_completing, this) ); @@ -164,7 +166,8 @@ var IPython = (function (IPython) { // let's build a function that wrap all that stuff into what is needed // for the new completer: var content = msg.content; - var matched_text = content.matched_text; + var start = content.cursor_start; + var end = content.cursor_end; var matches = content.matches; var cur = this.editor.getCursor(); @@ -172,7 +175,8 @@ var IPython = (function (IPython) { var filtered_results = []; //remove results from context completion //that are already in kernel completion - for (var i=0; i < results.length; i++) { + var i; + for (i=0; i < results.length; i++) { if (!_existing_completion(results[i].str, matches)) { filtered_results.push(results[i]); } @@ -181,18 +185,12 @@ var IPython = (function (IPython) { // append the introspection result, in order, at at the beginning of // the table and compute the replacement range from current cursor // positon and matched_text length. - for (var i = matches.length - 1; i >= 0; --i) { + for (i = matches.length - 1; i >= 0; --i) { filtered_results.unshift({ str: matches[i], type: "introspection", - from: { - line: cur.line, - ch: cur.ch - matched_text.length - }, - to: { - line: cur.line, - ch: cur.ch - } + from: utils.from_absolute_cursor_pos(this.editor, start), + to: utils.from_absolute_cursor_pos(this.editor, end) }); } @@ -256,8 +254,9 @@ var IPython = (function (IPython) { // After everything is on the page, compute the postion. // We put it above the code if it is too close to the bottom of the page. - cur.ch = cur.ch-matched_text.length; - var pos = this.editor.cursorCoords(cur); + var pos = this.editor.cursorCoords( + utils.from_absolute_cursor_pos(this.editor, start) + ); var left = pos.left-3; var top; var cheight = this.complete.height(); diff --git a/IPython/html/static/notebook/js/tooltip.js b/IPython/html/static/notebook/js/tooltip.js index f9d9b6a..066f709 100644 --- a/IPython/html/static/notebook/js/tooltip.js +++ b/IPython/html/static/notebook/js/tooltip.js @@ -229,7 +229,7 @@ var IPython = (function (IPython) { this.cancel_pending(); var editor = cell.code_mirror; var cursor = editor.getCursor(); - var cursor_pos = IPython.utils.absolute_cursor_pos(editor, cursor); + var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor); var text = cell.get_text(); this._hide_if_no_docstring = hide_if_no_docstring; diff --git a/IPython/kernel/tests/test_message_spec.py b/IPython/kernel/tests/test_message_spec.py index 36a0857..9c9da71 100644 --- a/IPython/kernel/tests/test_message_spec.py +++ b/IPython/kernel/tests/test_message_spec.py @@ -138,6 +138,9 @@ class Status(Reference): class CompleteReply(Reference): matches = List(Unicode) + cursor_start = Integer() + cursor_end = Integer() + status = Unicode() class KernelInfoReply(Reference): diff --git a/IPython/kernel/zmq/ipkernel.py b/IPython/kernel/zmq/ipkernel.py index 27a3557..5f20b23 100755 --- a/IPython/kernel/zmq/ipkernel.py +++ b/IPython/kernel/zmq/ipkernel.py @@ -496,7 +496,9 @@ class Kernel(Configurable): txt, matches = self.shell.complete('', code, cursor_pos) matches = {'matches' : matches, - 'matched_text' : txt, + 'cursor_end' : cursor_pos, + 'cursor_start' : cursor_pos - len(txt), + 'metadata' : {}, 'status' : 'ok'} matches = json_clean(matches) completion_msg = self.session.send(stream, 'complete_reply', diff --git a/IPython/qt/console/ipython_widget.py b/IPython/qt/console/ipython_widget.py index 1f51dbc..1a0a757 100644 --- a/IPython/qt/console/ipython_widget.py +++ b/IPython/qt/console/ipython_widget.py @@ -146,22 +146,12 @@ class IPythonWidget(FrontendWidget): info = self._request_info.get('complete') if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): - matches = rep['content']['matches'] - text = rep['content']['matched_text'] - offset = len(text) - - # Clean up matches with period and path separators if the matched - # text has not been transformed. This is done by truncating all - # but the last component and then suitably decreasing the offset - # between the current cursor position and the start of completion. - if len(matches) > 1 and matches[0][:offset] == text: - parts = re.split(r'[./\\]', text) - sep_count = len(parts) - 1 - if sep_count: - chop_length = sum(map(len, parts[:sep_count])) + sep_count - matches = [ match[chop_length:] for match in matches ] - offset -= chop_length - + content = rep['content'] + matches = content['matches'] + start = content['cursor_start'] + end = content['cursor_end'] + + offset = end - start # Move the cursor to the start of the match and complete. cursor.movePosition(QtGui.QTextCursor.Left, n=offset) self._complete_with_items(cursor, matches)