ソースを参照

Merge pull request #870 from afshin/completer

Completer refactor.
Brian E. Granger 8 年 前
コミット
00ec814a8a

+ 2 - 2
src/application/shell.ts

@@ -386,9 +386,9 @@ class SideBarHandler {
       newWidget.show();
     }
     if (newWidget) {
-      document.body.dataset[`${this._side}Area`] = newWidget.id;
+      document.body.setAttribute(`data-${this._side}Area`, newWidget.id);
     } else {
-      delete document.body.dataset[`${this._side}Area`];
+      document.body.removeAttribute(`data-${this._side}Area`);
     }
     this._refreshVisibility();
   }

+ 36 - 23
src/notebook/completion/handler.ts → src/completer/handler.ts

@@ -11,32 +11,33 @@ import {
 
 import {
   ICellEditorWidget, ITextChange, ICompletionRequest
-} from '../cells/editor';
+} from '../notebook/cells/editor';
 
 import {
   BaseCellWidget
-} from '../cells/widget';
+} from '../notebook/cells/widget';
 
 import {
-  CompletionWidget
+  CompleterWidget
 } from './widget';
 
 
 /**
- * A completion handler for cell widgets.
+ * A completer handler for cell widgets.
  */
 export
-class CellCompletionHandler implements IDisposable {
+class CellCompleterHandler implements IDisposable {
   /**
-   * Construct a new completion handler for a widget.
+   * Construct a new completer handler for a widget.
    */
-  constructor(completion: CompletionWidget) {
-    this._completion = completion;
-    this._completion.selected.connect(this.onCompletionSelected, this);
+  constructor(completer: CompleterWidget) {
+    this._completer = completer;
+    this._completer.selected.connect(this.onCompletionSelected, this);
+    this._completer.visibilityChanged.connect(this.onVisibilityChanged, this);
   }
 
   /**
-   * The kernel used by the completion handler.
+   * The kernel used by the completer handler.
    */
   get kernel(): IKernel {
     return this._kernel;
@@ -46,7 +47,7 @@ class CellCompletionHandler implements IDisposable {
   }
 
   /**
-   * The cell widget used by the completion handler.
+   * The cell widget used by the completer handler.
    */
   get activeCell(): BaseCellWidget {
     return this._activeCell;
@@ -70,13 +71,13 @@ class CellCompletionHandler implements IDisposable {
   }
 
   /**
-   * Get whether the completion handler is disposed.
+   * Get whether the completer handler is disposed.
    *
    * #### Notes
    * This is a read-only property.
    */
   get isDisposed(): boolean {
-    return this._completion === null;
+    return this._completer === null;
   }
 
   /**
@@ -86,13 +87,13 @@ class CellCompletionHandler implements IDisposable {
     if (this.isDisposed) {
       return;
     }
-    this._completion = null;
+    this._completer = null;
     this._kernel = null;
     this._activeCell = null;
   }
 
   /**
-   * Make a completion request using the kernel.
+   * Make a complete request using the kernel.
    */
   protected makeRequest(request: ICompletionRequest): Promise<void> {
     if (!this._kernel) {
@@ -123,7 +124,7 @@ class CellCompletionHandler implements IDisposable {
       return;
     }
     let value = msg.content;
-    let model = this._completion.model;
+    let model = this._completer.model;
     // Completion request failures or negative results fail silently.
     if (value.status !== 'ok') {
       model.reset();
@@ -141,30 +142,42 @@ class CellCompletionHandler implements IDisposable {
    * Handle a text changed signal from an editor.
    */
   protected onTextChanged(editor: ICellEditorWidget, change: ITextChange): void {
-    if (!this._completion.model) {
+    if (!this._completer.model) {
       return;
     }
-    this._completion.model.handleTextChange(change);
+    this._completer.model.handleTextChange(change);
   }
 
   /**
    * Handle a completion requested signal from an editor.
    */
   protected onCompletionRequested(editor: ICellEditorWidget, request: ICompletionRequest): void {
-    if (!this.kernel || !this._completion.model) {
+    if (!this.kernel || !this._completer.model) {
       return;
     }
     this.makeRequest(request);
   }
 
+
+  /**
+   * Handle a visiblity change signal from a completer widget.
+   */
+  protected onVisibilityChanged(completer: CompleterWidget): void {
+    if (completer.isDisposed || completer.isHidden) {
+      if (this._activeCell) {
+        this._activeCell.activate();
+      }
+    }
+  }
+
   /**
    * Handle a completion selected signal from the completion widget.
    */
-  protected onCompletionSelected(widget: CompletionWidget, value: string): void {
-    if (!this._activeCell || !this._completion.model) {
+  protected onCompletionSelected(widget: CompleterWidget, value: string): void {
+    if (!this._activeCell || !this._completer.model) {
       return;
     }
-    let patch = this._completion.model.createPatch(value);
+    let patch = this._completer.model.createPatch(value);
     if (!patch) {
       return;
     }
@@ -174,7 +187,7 @@ class CellCompletionHandler implements IDisposable {
   }
 
   private _activeCell: BaseCellWidget = null;
-  private _completion: CompletionWidget = null;
+  private _completer: CompleterWidget = null;
   private _kernel: IKernel = null;
   private _pending = 0;
 }

+ 58 - 0
src/completer/index.css

@@ -0,0 +1,58 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) 2014-2016, Jupyter Development Team.
+|
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+
+:root {
+  --jp-private-completer-code-font-size: 14px;
+  --jp-private-completer-item-height: 24px;
+}
+
+
+.jp-Completer {
+  background: var(--jp-layout-color1);
+  border: var(--jp-border-width) solid var(--jp-border-color2);
+  box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2);
+  list-style-type: none;
+  overflow: auto;
+  padding: 0;
+  margin: 4px 0 0 -4px;
+  max-height: calc((10 * var(--jp-private-completer-item-height)) + (2 * var(--jp-border-width)));
+  min-height: calc(var(--jp-private-completer-item-height) + (2 * var(--jp-border-width)));
+  position: absolute;
+  z-index: 10001;
+}
+
+
+.jp-Completer.jp-mod-outofview {
+  display: none;
+}
+
+
+.jp-Completer-item {
+  margin: 0;
+  min-width: 150px;
+  padding: 0 2px;
+}
+
+
+.jp-Completer-item:hover, .jp-Completer-item.jp-mod-active {
+  background: var(--jp-layout-color2);
+}
+
+
+.jp-Completer-item code {
+  font-size: var(--jp-private-completer-code-font-size);
+  height: var(--jp-private-completer-item-height);
+  padding: 0;
+  margin: 0;
+}
+
+
+.jp-Completer-item mark {
+  font-weight: bold;
+  background: inherit;
+  color: inherit;
+}

+ 0 - 0
src/notebook/completion/index.ts → src/completer/index.ts


+ 62 - 41
src/notebook/completion/model.ts → src/completer/model.ts

@@ -19,7 +19,7 @@ import {
 
 import {
   ICompletionRequest, ITextChange
-} from '../cells/editor';
+} from '../notebook/cells/editor';
 
 
 /**
@@ -63,17 +63,17 @@ interface ICompletionPatch {
 
 
 /**
- * A completion menu item.
+ * A completer menu item.
  */
 export
-interface ICompletionItem {
+interface ICompleterItem {
   /**
-   * The highlighted, marked up text of a visible completion item.
+   * The highlighted, marked up text of a visible completer item.
    */
   text: string;
 
   /**
-   * The raw text of a visible completion item.
+   * The raw text of a visible completer item.
    */
   raw: string;
 }
@@ -97,14 +97,14 @@ interface ICursorSpan extends JSONObject {
 
 
 /**
- * The data model backing a code completion widget.
+ * The data model backing a code completer widget.
  */
 export
-interface ICompletionModel extends IDisposable {
+interface ICompleterModel extends IDisposable {
   /**
-   * A signal emitted when state of the completion menu changes.
+   * A signal emitted when state of the completer menu changes.
    */
-  stateChanged: ISignal<ICompletionModel, void>;
+  stateChanged: ISignal<ICompleterModel, void>;
 
   /**
    * The current text change details.
@@ -117,17 +117,22 @@ interface ICompletionModel extends IDisposable {
   cursor: ICursorSpan;
 
   /**
-   * The list of visible items in the completion menu.
+   * A flag that is true when the model value was modified by a subset match.
    */
-  items: ICompletionItem[];
+  subsetMatch: boolean;
 
   /**
-   * The unfiltered list of all available options in a completion menu.
+   * The list of visible items in the completer menu.
+   */
+  items: ICompleterItem[];
+
+  /**
+   * The unfiltered list of all available options in a completer menu.
    */
   options: string[];
 
   /**
-   * The original completion request details.
+   * The original completer request details.
    */
   original: ICompletionRequest;
 
@@ -154,27 +159,27 @@ interface ICompletionModel extends IDisposable {
 
 
 /**
- * An implementation of a completion model.
+ * An implementation of a completer model.
  */
 export
-class CompletionModel implements ICompletionModel {
+class CompleterModel implements ICompleterModel {
   /**
-   * A signal emitted when state of the completion menu changes.
+   * A signal emitted when state of the completer menu changes.
    */
-  stateChanged: ISignal<ICompletionModel, void>;
+  stateChanged: ISignal<ICompleterModel, void>;
 
   /**
-   * The list of visible items in the completion menu.
+   * The list of visible items in the completer menu.
    *
    * #### Notes
    * This is a read-only property.
    */
-  get items(): ICompletionItem[] {
+  get items(): ICompleterItem[] {
     return this._filter();
   }
 
   /**
-   * The unfiltered list of all available options in a completion menu.
+   * The unfiltered list of all available options in a completer menu.
    */
   get options(): string[] {
     return this._options;
@@ -186,6 +191,7 @@ class CompletionModel implements ICompletionModel {
     if (newValue && newValue.length) {
       this._options = [];
       this._options.push(...newValue);
+      this._subsetMatch = true;
     } else {
       this._options = null;
     }
@@ -217,26 +223,23 @@ class CompletionModel implements ICompletionModel {
     if (deepEqual(newValue, this._current)) {
       return;
     }
-
     // Original request must always be set before a text change. If it isn't
     // the model fails silently.
     if (!this.original) {
       return;
     }
-
     // Cursor must always be set before a text change. This happens
-    // automatically in the completion handler, but since `current` is a public
+    // automatically in the completer handler, but since `current` is a public
     // attribute, this defensive check is necessary.
     if (!this._cursor) {
       return;
     }
     this._current = newValue;
 
-    if (!this.current) {
+    if (!this._current) {
       this.stateChanged.emit(void 0);
       return;
     }
-
     let original = this._original;
     let current = this._current;
     let originalLine = original.currentValue.split('\n')[original.line];
@@ -247,15 +250,15 @@ class CompletionModel implements ICompletionModel {
     if (currentLine.length < originalLine.length) {
       this.reset();
       return;
-    } else {
-      let { start, end } = this._cursor;
-      // Clip the front of the current line.
-      let query = currentLine.substring(start);
-      // Clip the back of the current line.
-      let ending = originalLine.substring(end);
-      query = query.substring(0, query.lastIndexOf(ending));
-      this._query = query;
     }
+
+    let { start, end } = this._cursor;
+    // Clip the front of the current line.
+    let query = current.newValue.substring(start);
+    // Clip the back of the current line.
+    let ending = original.currentValue.substring(end);
+    query = query.substring(0, query.lastIndexOf(ending));
+    this._query = query;
     this.stateChanged.emit(void 0);
   }
 
@@ -285,6 +288,16 @@ class CompletionModel implements ICompletionModel {
     this._query = newValue;
   }
 
+  /**
+   * A flag that is true when the model value was modified by a subset match.
+   */
+  get subsetMatch(): boolean {
+    return this._subsetMatch;
+  }
+  set subsetMatch(newValue: boolean) {
+    this._subsetMatch = newValue;
+  }
+
   /**
    * Get whether the model is disposed.
    */
@@ -309,6 +322,12 @@ class CompletionModel implements ICompletionModel {
    * Handle a text change.
    */
   handleTextChange(change: ITextChange): void {
+    // When the completer detects a common subset prefix for all options,
+    // it updates the model and sets the model source to that value, but this
+    // text change should be ignored.
+    if (this.subsetMatch) {
+      return;
+    }
     let line = change.newValue.split('\n')[change.line];
     // If last character entered is not whitespace, update completion.
     if (line[change.ch - 1] && line[change.ch - 1].match(/\S/)) {
@@ -358,7 +377,7 @@ class CompletionModel implements ICompletionModel {
   /**
    * Apply the query to the complete options list to return the matching subset.
    */
-  private _filter(): ICompletionItem[] {
+  private _filter(): ICompleterItem[] {
     let options = this._options || [];
     let query = this._query;
     if (!query) {
@@ -384,27 +403,29 @@ class CompletionModel implements ICompletionModel {
    */
   private _reset(): void {
     this._current = null;
-    this._original = null;
-    this._options = null;
     this._cursor = null;
+    this._options = null;
+    this._original = null;
     this._query = '';
+    this._subsetMatch = false;
   }
 
+  private _current: ITextChange = null;
+  private _cursor: ICursorSpan = null;
   private _isDisposed = false;
   private _options: string[] = null;
   private _original: ICompletionRequest = null;
-  private _current: ITextChange = null;
   private _query = '';
-  private _cursor: ICursorSpan = null;
+  private _subsetMatch = false;
 }
 
 
-// Define the signals for the `CompletionModel` class.
-defineSignal(CompletionModel.prototype, 'stateChanged');
+// Define the signals for the `CompleterModel` class.
+defineSignal(CompleterModel.prototype, 'stateChanged');
 
 
 /**
- * A namespace for completion model private data.
+ * A namespace for completer model private data.
  */
 namespace Private {
   /**

+ 140 - 76
src/notebook/completion/widget.ts → src/completer/widget.ts

@@ -18,39 +18,43 @@ import {
 } from 'phosphor/lib/ui/widget';
 
 import {
-  ICompletionModel, ICompletionItem
+  ICompleterModel, ICompleterItem
 } from './model';
 
 
 /**
- * The class name added to completion menu widgets.
+ * The class name added to completer menu widgets.
  */
-const COMPLETION_CLASS = 'jp-Completion';
+const COMPLETER_CLASS = 'jp-Completer';
 
 /**
- * The class name added to completion menu items.
+ * The class name added to completer menu items.
  */
-const ITEM_CLASS = 'jp-Completion-item';
+const ITEM_CLASS = 'jp-Completer-item';
 
 /**
- * The class name added to an active completion menu item.
+ * The class name added to an active completer menu item.
  */
 const ACTIVE_CLASS = 'jp-mod-active';
 
 /**
- * The class name added to a completion widget that is scrolled out of view.
+ * The class name added to a completer widget that is scrolled out of view.
  */
 const OUTOFVIEW_CLASS = 'jp-mod-outofview';
 
 /**
- * The minimum height of a completion widget.
+ * The minimum height of a completer widget.
  */
-const MIN_HEIGHT = 75;
+const MIN_HEIGHT = 20;
 
 /**
- * The maximum height of a completion widget.
+ * The maximum height of a completer widget.
+ *
+ * #### Notes
+ * This value is only used if a CSS max-height attribute is not set for the
+ * completer. It is a fallback value.
  */
-const MAX_HEIGHT = 250;
+const MAX_HEIGHT = 200;
 
 /**
  * A flag to indicate that event handlers are caught in the capture phase.
@@ -62,45 +66,45 @@ const USE_CAPTURE = true;
  * A widget that enables text completion.
  */
 export
-class CompletionWidget extends Widget {
+class CompleterWidget extends Widget {
   /**
-   * Construct a text completion menu widget.
+   * Construct a text completer menu widget.
    */
-  constructor(options: CompletionWidget.IOptions = {}) {
+  constructor(options: CompleterWidget.IOptions = {}) {
     super({ node: document.createElement('ul') });
-    this._renderer = options.renderer || CompletionWidget.defaultRenderer;
+    this._renderer = options.renderer || CompleterWidget.defaultRenderer;
     this.anchor = options.anchor || null;
     this.model = options.model || null;
-    this.addClass(COMPLETION_CLASS);
+    this.addClass(COMPLETER_CLASS);
 
-    // Completion widgets are hidden until they are populated.
+    // Completer widgets are hidden until they are populated.
     this.hide();
   }
 
   /**
-   * A signal emitted when a selection is made from the completion menu.
+   * A signal emitted when a selection is made from the completer menu.
    */
-  selected: ISignal<CompletionWidget, string>;
+  selected: ISignal<CompleterWidget, string>;
 
   /**
-   * A signal emitted when the completion widget's visibility changes.
+   * A signal emitted when the completer widget's visibility changes.
    *
    * #### Notes
    * This signal is useful when there are multiple floating widgets that may
    * contend with the same space and ought to be mutually exclusive.
    */
-  visibilityChanged: ISignal<CompletionWidget, void>;
+  visibilityChanged: ISignal<CompleterWidget, void>;
 
   /**
-   * The model used by the completion widget.
+   * The model used by the completer widget.
    *
    * #### Notes
    * This is a read-only property.
    */
-  get model(): ICompletionModel {
+  get model(): ICompleterModel {
     return this._model;
   }
-  set model(model: ICompletionModel) {
+  set model(model: ICompleterModel) {
     if (!model && !this._model || model === this._model) {
       return;
     }
@@ -114,10 +118,10 @@ class CompletionWidget extends Widget {
   }
 
   /**
-   * The semantic parent of the completion widget, its anchor element. An
-   * event listener will peg the position of the completion widget to the
+   * The semantic parent of the completer widget, its anchor element. An
+   * event listener will peg the position of the completer widget to the
    * anchor element's scroll position. Other event listeners will guarantee
-   * the completion widget behaves like a child of the reference element even
+   * the completer widget behaves like a child of the reference element even
    * if it does not appear as a descendant in the DOM.
    */
   get anchor(): HTMLElement {
@@ -141,7 +145,7 @@ class CompletionWidget extends Widget {
   }
 
   /**
-   * Dispose of the resources held by the completion widget.
+   * Dispose of the resources held by the completer widget.
    */
   dispose() {
     if (this.isDisposed) {
@@ -155,13 +159,10 @@ class CompletionWidget extends Widget {
    * Reset the widget.
    */
   reset(): void {
+    this._reset();
     if (this._model) {
       this._model.reset();
     }
-    this._activeIndex = 0;
-    this._anchorPoint = 0;
-    this.hide();
-    this.visibilityChanged.emit(void 0);
   }
 
   /**
@@ -226,8 +227,9 @@ class CompletionWidget extends Widget {
    * Handle `update_request` messages.
    */
   protected onUpdateRequest(msg: Message): void {
-    let model = this.model;
-    if (!model) {
+    let model = this._model;
+    let anchor = this._anchor;
+    if (!model || !anchor) {
       return;
     }
 
@@ -235,6 +237,7 @@ class CompletionWidget extends Widget {
 
     // If there are no items, reset and bail.
     if (!items || !items.length) {
+      this._reset();
       this.hide();
       this.visibilityChanged.emit(void 0);
       return;
@@ -247,13 +250,15 @@ class CompletionWidget extends Widget {
       return;
     }
 
+    // Clear the node.
     let node = this.node;
     node.textContent = '';
 
+    // Populate the completer items.
     for (let item of items) {
       let li = this._renderer.createItemNode(item);
       // Set the raw, un-marked up value as a data attribute.
-      li.dataset['value'] = item.raw;
+      li.setAttribute('data-value', item.raw);
       node.appendChild(li);
     }
 
@@ -264,12 +269,19 @@ class CompletionWidget extends Widget {
       this.show();
       this.visibilityChanged.emit(void 0);
     }
-    this._anchorPoint = this._anchor.scrollTop;
+    this._anchorPoint = anchor.scrollTop;
     this._setGeometry();
+
+    // If this is the first time the current completer session has loaded,
+    // populate any initial subset match.
+    if (this._model.subsetMatch) {
+      this._populateSubset();
+      this.model.subsetMatch = false;
+    }
   }
 
   /**
-   * Cycle through the available completion items.
+   * Cycle through the available completer items.
    */
   private _cycle(direction: 'up' | 'down'): void {
     let items = this.node.querySelectorAll(`.${ITEM_CLASS}`);
@@ -301,7 +313,10 @@ class CompletionWidget extends Widget {
             event.preventDefault();
             event.stopPropagation();
             event.stopImmediatePropagation();
-            if (this._populateSubset()) {
+            this._model.subsetMatch = true;
+            let populated = this._populateSubset();
+            this.model.subsetMatch = false;
+            if (populated) {
               return;
             }
             this._selectActive();
@@ -353,7 +368,7 @@ class CompletionWidget extends Widget {
         event.preventDefault();
         event.stopPropagation();
         event.stopImmediatePropagation();
-        this.selected.emit(target.dataset['value']);
+        this.selected.emit(target.getAttribute('data-value'));
         this.reset();
         return;
       }
@@ -380,7 +395,7 @@ class CompletionWidget extends Widget {
   }
 
   /**
-   * Populate the completion up to the longest initial subset of items.
+   * Populate the completer up to the longest initial subset of items.
    *
    * @returns `true` if a subset match was found and populated.
    */
@@ -397,40 +412,88 @@ class CompletionWidget extends Widget {
     return false;
   }
 
+  /**
+   * Reset the internal flags to defaults.
+   */
+  private _reset(): void {
+    this._activeIndex = 0;
+    this._anchorPoint = 0;
+  }
+
   /**
    * Set the visible dimensions of the widget.
    */
   private _setGeometry(): void {
-    if (!this.model || !this._model.original) {
+    let node = this.node;
+    let model = this._model;
+
+    // This is an overly defensive test: `cursor` will always exist if
+    // `original` exists, except in contrived tests. But since it is possible
+    // to generate a runtime error, the check occurs here.
+    if (!model || !model.original || !model.cursor) {
       return;
     }
 
-    let node = this.node;
-    let coords = this._model.current ? this._model.current.coords
-      : this._model.original.coords;
-    let scrollDelta = this._anchorPoint - this._anchor.scrollTop;
-    let availableHeight = coords.top + scrollDelta;
-    let maxHeight = Math.max(0, Math.min(availableHeight, MAX_HEIGHT));
+    // Clear any previous set max-height.
+    node.style.maxHeight = '';
 
-    if (maxHeight > MIN_HEIGHT) {
-      node.classList.remove(OUTOFVIEW_CLASS);
+    // Clear any programmatically set margin-top.
+    node.style.marginTop = '';
+
+    // Make sure the node is visible.
+    node.classList.remove(OUTOFVIEW_CLASS);
+
+    // Always use original coordinates to calculate completer position.
+    let { coords, chWidth, chHeight } = model.original;
+    let style = window.getComputedStyle(node);
+    let innerHeight = window.innerHeight;
+    let scrollDelta = this._anchorPoint - this._anchor.scrollTop;
+    let spaceAbove = coords.top + scrollDelta;
+    let spaceBelow = innerHeight - coords.bottom - scrollDelta;
+    let marginTop = (parseInt(style.marginTop, 10) || 0);
+    let maxHeight = (parseInt(style.maxHeight, 10) || MAX_HEIGHT);
+    let minHeight = (parseInt(style.minHeight, 10) || MIN_HEIGHT);
+    let anchorRect = this._anchor.getBoundingClientRect();
+    let top: number;
+
+    // If the whole completer fits below or if there is more space below, then
+    // rendering the completer below the text being typed is privileged so that
+    // the code above is not obscured.
+    let renderBelow = spaceBelow >= maxHeight || spaceBelow >= spaceAbove;
+    if (renderBelow) {
+      maxHeight = Math.min(spaceBelow - marginTop, maxHeight);
     } else {
+      maxHeight = Math.min(spaceAbove, maxHeight);
+      // If the completer renders above the text, its top margin is irrelevant.
+      node.style.marginTop = '0px';
+    }
+    node.style.maxHeight = `${maxHeight}px`;
+
+    // Make sure the completer ought to be visible.
+    let withinBounds = maxHeight > minHeight &&
+                   spaceBelow >= chHeight &&
+                   spaceAbove >= anchorRect.top;
+    if (!withinBounds) {
       node.classList.add(OUTOFVIEW_CLASS);
       return;
     }
-    node.style.maxHeight = `${maxHeight}px`;
 
-    let borderLeftWidth = window.getComputedStyle(node).borderLeftWidth;
+    let borderLeftWidth = style.borderLeftWidth;
     let left = coords.left + (parseInt(borderLeftWidth, 10) || 0);
-    let rect = node.getBoundingClientRect();
-    let top = availableHeight - rect.height;
+    let { start, end } = this._model.cursor;
+    let nodeRect = node.getBoundingClientRect();
 
-    node.style.left = `${Math.floor(left)}px`;
+    // Position the completer vertically.
+    top = renderBelow ? innerHeight - spaceBelow : spaceAbove - nodeRect.height;
     node.style.top = `${Math.floor(top)}px`;
+
+    // Move completer to the start of the blob being completed.
+    left -= chWidth * (end - start);
+    node.style.left = `${Math.ceil(left)}px`;
     node.style.width = 'auto';
 
     // Expand the menu width by the scrollbar size, if present.
-    if (node.scrollHeight > maxHeight) {
+    if (node.scrollHeight >= maxHeight) {
       node.style.width = `${2 * node.offsetWidth - node.clientWidth}px`;
       node.scrollTop = 0;
     }
@@ -442,61 +505,62 @@ class CompletionWidget extends Widget {
   private _selectActive(): void {
     let active = this.node.querySelector(`.${ACTIVE_CLASS}`) as HTMLElement;
     if (!active) {
+      this._reset();
       return;
     }
-    this.selected.emit(active.dataset['value']);
+    this.selected.emit(active.getAttribute('data-value'));
     this.reset();
   }
 
   private _anchor: HTMLElement = null;
   private _anchorPoint = 0;
   private _activeIndex = 0;
-  private _model: ICompletionModel = null;
-  private _renderer: CompletionWidget.IRenderer = null;
+  private _model: ICompleterModel = null;
+  private _renderer: CompleterWidget.IRenderer = null;
 }
 
 
-// Define the signals for the `CompletionWidget` class.
-defineSignal(CompletionWidget.prototype, 'selected');
-defineSignal(CompletionWidget.prototype, 'visibilityChanged');
+// Define the signals for the `CompleterWidget` class.
+defineSignal(CompleterWidget.prototype, 'selected');
+defineSignal(CompleterWidget.prototype, 'visibilityChanged');
 
 
 export
-namespace CompletionWidget {
+namespace CompleterWidget {
   /**
-   * The initialization options for a completion widget.
+   * The initialization options for a completer widget.
    */
   export
   interface IOptions {
     /**
-     * The model for the completion widget.
+     * The model for the completer widget.
      */
-    model?: ICompletionModel;
+    model?: ICompleterModel;
 
     /**
-     * The semantic parent of the completion widget, its anchor element. An
-     * event listener will peg the position of the completion widget to the
+     * The semantic parent of the completer widget, its anchor element. An
+     * event listener will peg the position of the completer widget to the
      * anchor element's scroll position. Other event listeners will guarantee
-     * the completion widget behaves like a child of the reference element even
+     * the completer widget behaves like a child of the reference element even
      * if it does not appear as a descendant in the DOM.
      */
     anchor?: HTMLElement;
 
     /**
-     * The renderer for the completion widget nodes.
+     * The renderer for the completer widget nodes.
      */
     renderer?: IRenderer;
   }
 
   /**
-   * A renderer for completion widget nodes.
+   * A renderer for completer widget nodes.
    */
   export
   interface IRenderer {
     /**
-     * Create an item node (an `li` element) for a text completion menu.
+     * Create an item node (an `li` element) for a text completer menu.
      */
-    createItemNode(item: ICompletionItem): HTMLLIElement;
+    createItemNode(item: ICompleterItem): HTMLLIElement;
   }
 
   /**
@@ -505,9 +569,9 @@ namespace CompletionWidget {
   export
   class Renderer implements IRenderer {
     /**
-     * Create an item node for a text completion menu.
+     * Create an item node for a text completer menu.
      */
-    createItemNode(item: ICompletionItem): HTMLLIElement {
+    createItemNode(item: ICompleterItem): HTMLLIElement {
       let li = document.createElement('li');
       let code = document.createElement('code');
 
@@ -530,7 +594,7 @@ namespace CompletionWidget {
 
 
 /**
- * A namespace for completion widget private data.
+ * A namespace for completer widget private data.
  */
 namespace Private {
   /**
@@ -563,7 +627,7 @@ namespace Private {
   function itemValues(items: NodeList): string[] {
     let values: string[] = [];
     for (let i = 0, len = items.length; i < len; i++) {
-      values.push((items[i] as HTMLElement).dataset['value']);
+      values.push((items[i] as HTMLElement).getAttribute('data-value'));
     }
     return values;
   }

+ 32 - 32
src/console/content.ts

@@ -46,8 +46,8 @@ import {
 } from '../notebook/common/mimetype';
 
 import {
-  CompletionWidget, CompletionModel, CellCompletionHandler
-} from '../notebook/completion';
+  CompleterWidget, CompleterModel, CellCompleterHandler
+} from '../completer';
 
 import {
   IRenderMime
@@ -126,23 +126,23 @@ class ConsoleContent extends Widget {
     this._session = options.session;
     this._history = new ConsoleHistory({ kernel: this._session.kernel });
 
-    // Instantiate tab completion widget.
-    let completion = options.completion || new CompletionWidget({
-      model: new CompletionModel()
+    // Instantiate tab completer widget.
+    let completer = options.completer || new CompleterWidget({
+      model: new CompleterModel()
     });
-    this._completion = completion;
+    this._completer = completer;
 
-    // Set the completion widget's anchor node to peg its position.
-    completion.anchor = this.node;
+    // Set the completer widget's anchor node to peg its position.
+    completer.anchor = this.node;
 
-    // Because a completion widget may be passed in, check if it is attached.
-    if (!completion.isAttached) {
-      Widget.attach(completion, document.body);
+    // Because a completer widget may be passed in, check if it is attached.
+    if (!completer.isAttached) {
+      Widget.attach(completer, document.body);
     }
 
-    // Set up the completion handler.
-    this._completionHandler = new CellCompletionHandler(this._completion);
-    this._completionHandler.kernel = this._session.kernel;
+    // Set up the completer handler.
+    this._completerHandler = new CellCompleterHandler(this._completer);
+    this._completerHandler.kernel = this._session.kernel;
 
     // Set up the inspection handler.
     this._inspectionHandler = new InspectionHandler(this._rendermime);
@@ -173,7 +173,7 @@ class ConsoleContent extends Widget {
       this.initialize();
       this._history.dispose();
       this._history = new ConsoleHistory(kernel);
-      this._completionHandler.kernel = kernel;
+      this._completerHandler.kernel = kernel;
       this._inspectionHandler.kernel = kernel;
       this._foreignCells = {};
       this.monitorForeignIOPub();
@@ -201,7 +201,7 @@ class ConsoleContent extends Widget {
         cell.model.executionCount = inputMsg.content.execution_count;
         cell.model.source = inputMsg.content.code;
         cell.trusted = true;
-        this.update()
+        this.update();
         break;
       case 'execute_result':
       case 'clear_output':
@@ -270,10 +270,10 @@ class ConsoleContent extends Widget {
     }
     this._history.dispose();
     this._history = null;
-    this._completionHandler.dispose();
-    this._completionHandler = null;
-    this._completion.dispose();
-    this._completion = null;
+    this._completerHandler.dispose();
+    this._completerHandler = null;
+    this._completer.dispose();
+    this._completer = null;
     this._inspectionHandler.dispose();
     this._inspectionHandler = null;
     this._session.dispose();
@@ -289,7 +289,7 @@ class ConsoleContent extends Widget {
    * completeness.
    */
   execute(force=false): Promise<void> {
-    this.dismissCompletion();
+    this.dismissCompleter();
 
     if (this._session.status === 'dead') {
       this._inspectionHandler.handleExecuteReply(null);
@@ -331,10 +331,10 @@ class ConsoleContent extends Widget {
   }
 
   /**
-   * Dismiss the completion widget for a console.
+   * Dismiss the completer widget for a console.
    */
-  dismissCompletion(): void {
-    this._completion.reset();
+  dismissCompleter(): void {
+    this._completer.reset();
   }
 
   /**
@@ -423,12 +423,12 @@ class ConsoleContent extends Widget {
     prompt.addClass(PROMPT_CLASS);
     this._input.addWidget(prompt);
 
-    // Hook up completion and history handling.
+    // Hook up completer and history handling.
     let editor = prompt.editor;
     editor.edgeRequested.connect(this.onEdgeRequest, this);
 
-    // Associate the new prompt with the completion and inspection handlers.
-    this._completionHandler.activeCell = prompt;
+    // Associate the new prompt with the completer and inspection handlers.
+    this._completerHandler.activeCell = prompt;
     this._inspectionHandler.activeCell = prompt;
 
     prompt.activate();
@@ -515,8 +515,8 @@ class ConsoleContent extends Widget {
     this.prompt.mimetype = this._mimetype;
   }
 
-  private _completion: CompletionWidget = null;
-  private _completionHandler: CellCompletionHandler = null;
+  private _completer: CompleterWidget = null;
+  private _completerHandler: CellCompleterHandler = null;
   private _content: Panel = null;
   private _input: Panel = null;
   private _inspectionHandler: InspectionHandler = null;
@@ -544,9 +544,9 @@ namespace ConsoleContent {
   export
   interface IOptions {
     /**
-     * The completion widget for a console content widget.
+     * The completer widget for a console content widget.
      */
-    completion?: CompletionWidget;
+    completer?: CompleterWidget;
 
     /**
      * The renderer for a console content widget.
@@ -565,7 +565,7 @@ namespace ConsoleContent {
   }
 
   /**
-   * A renderer for completion widget nodes.
+   * A renderer for completer widget nodes.
    */
   export
   interface IRenderer {

+ 2 - 2
src/console/plugin.ts

@@ -192,11 +192,11 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
   menu.addItem({ command });
 
 
-  command = 'console:dismiss-completion';
+  command = 'console:dismiss-completer';
   commands.addCommand(command, {
     execute: () => {
       if (tracker.currentWidget) {
-        tracker.currentWidget.content.dismissCompletion();
+        tracker.currentWidget.content.dismissCompleter();
       }
     }
   });

+ 1 - 0
src/default-theme/index.css

@@ -17,6 +17,7 @@
 @import '../about/index.css';
 @import '../codemirror/index.css';
 @import '../commandpalette/index.css';
+@import '../completer/index.css';
 @import '../console/index.css';
 @import '../csvwidget/index.css';
 @import '../dialog/index.css';

+ 0 - 44
src/notebook/completion.css

@@ -1,44 +0,0 @@
-/*-----------------------------------------------------------------------------
-| Copyright (c) Jupyter Development Team.
-| Distributed under the terms of the Modified BSD License.
-|----------------------------------------------------------------------------*/
-
-
-.jp-Completion {
-  background: #EEEEEE;
-  border: var(--jp-border-width) solid var(--jp-border-color1);
-  list-style-type: none;
-  overflow: auto;
-  padding: 0;
-}
-
-
-.jp-Completion-item {
-  margin: 0;
-  min-width: 150px;
-  padding: 0 2px;
-}
-
-
-.jp-Completion-item:nth-child(odd) {
-  background: #FFFFFF;
-}
-
-
-.jp-Completion-item.jp-mod-active {
-  background: #BFBFBF;
-}
-
-
-.jp-Completion-item code {
-  font-size: 14px;
-  line-height: 25px;
-}
-
-
-.jp-Completion-item mark {
-  font-weight: bold;
-  text-decoration: underline;
-  background: inherit;
-  color: inherit;
-}

+ 0 - 15
src/notebook/completion/index.css

@@ -1,15 +0,0 @@
-/*-----------------------------------------------------------------------------
-| Copyright (c) 2014-2016, Jupyter Development Team.
-|
-| Distributed under the terms of the Modified BSD License.
-|----------------------------------------------------------------------------*/
-.jp-Completion {
-  margin: 0;
-  position: absolute;
-  z-index: 10001;
-}
-
-
-.jp-Completion.jp-mod-outofview {
-  display: none;
-}

+ 0 - 1
src/notebook/index.css

@@ -5,7 +5,6 @@
 |----------------------------------------------------------------------------*/
 
 
-@import './completion.css';
 @import './toolbar.css';
 
 

+ 23 - 24
src/notebook/notebook/panel.ts

@@ -42,8 +42,8 @@ import {
 } from '../../rendermime';
 
 import {
-  CompletionWidget, CompletionModel, CellCompletionHandler
-} from '../completion';
+  CompleterWidget, CompleterModel, CellCompleterHandler
+} from '../../completer';
 
 import {
   INotebookModel
@@ -97,17 +97,17 @@ class NotebookPanel extends Widget {
     layout.addWidget(toolbar);
     layout.addWidget(this._content);
 
-    this._completion = this._renderer.createCompletion();
-    // The completion widget's anchor node is the node whose scrollTop is
-    // pegged to the completion widget's position.
-    this._completion.anchor = this._content.node;
-    Widget.attach(this._completion, document.body);
+    this._completer = this._renderer.createCompleter();
+    // The completer widget's anchor node is the node whose scrollTop is
+    // pegged to the completer widget's position.
+    this._completer.anchor = this._content.node;
+    Widget.attach(this._completer, document.body);
 
-    // Set up the completion handler.
-    this._completionHandler = new CellCompletionHandler(this._completion);
-    this._completionHandler.activeCell = this._content.activeCell;
+    // Set up the completer handler.
+    this._completerHandler = new CellCompleterHandler(this._completer);
+    this._completerHandler.activeCell = this._content.activeCell;
     this._content.activeCellChanged.connect((s, cell) => {
-      this._completionHandler.activeCell = cell;
+      this._completerHandler.activeCell = cell;
     });
   }
 
@@ -228,10 +228,10 @@ class NotebookPanel extends Widget {
     this._content = null;
     this._rendermime = null;
     this._clipboard = null;
-    this._completionHandler.dispose();
-    this._completionHandler = null;
-    this._completion.dispose();
-    this._completion = null;
+    this._completerHandler.dispose();
+    this._completerHandler = null;
+    this._completer.dispose();
+    this._completer = null;
     this._renderer = null;
     super.dispose();
   }
@@ -336,7 +336,7 @@ class NotebookPanel extends Widget {
    * Handle a change in the kernel by updating the document metadata.
    */
   private _onKernelChanged(context: IDocumentContext<INotebookModel>, kernel: IKernel): void {
-    this._completionHandler.kernel = kernel;
+    this._completerHandler.kernel = kernel;
     this.content.inspectionHandler.kernel = kernel;
     this.kernelChanged.emit(kernel);
     if (!this.model || !kernel) {
@@ -393,8 +393,8 @@ class NotebookPanel extends Widget {
   }
 
   private _clipboard: IClipboard = null;
-  private _completion: CompletionWidget = null;
-  private _completionHandler: CellCompletionHandler = null;
+  private _completer: CompleterWidget = null;
+  private _completerHandler: CellCompleterHandler = null;
   private _content: Notebook = null;
   private _context: IDocumentContext<INotebookModel> = null;
   private _renderer: NotebookPanel.IRenderer = null;
@@ -451,9 +451,9 @@ export namespace NotebookPanel {
     createToolbar(): Toolbar;
 
     /**
-     * Create a new completion widget for the panel.
+     * Create a new completer widget for the panel.
      */
-    createCompletion(): CompletionWidget;
+    createCompleter(): CompleterWidget;
   }
 
   /**
@@ -474,11 +474,10 @@ export namespace NotebookPanel {
     }
 
     /**
-     * Create a new completion widget.
+     * Create a new completer widget.
      */
-    createCompletion(): CompletionWidget {
-      let model = new CompletionModel();
-      return new CompletionWidget({ model });
+    createCompleter(): CompleterWidget {
+      return new CompleterWidget({ model: new CompleterModel() });
     }
   }
 

+ 0 - 5
src/shortcuts/plugin.ts

@@ -265,11 +265,6 @@ const SHORTCUTS = [
     selector: '.jp-ConsolePanel',
     keys: ['Ctrl Enter']
   },
-  {
-    command: 'console:dismiss-completion',
-    selector: '.jp-ConsolePanel',
-    keys: ['Escape']
-  },
   {
     command: 'console:toggle-inspectors',
     selector: '.jp-ConsolePanel',

+ 88 - 75
test/src/notebook/completion/handler.spec.ts → test/src/completer/handler.spec.ts

@@ -13,22 +13,22 @@ import {
 
 import {
   BaseCellWidget, CellModel
-} from '../../../../lib/notebook/cells';
+} from '../../../lib/notebook/cells';
 
 import {
   ICompletionRequest, ICellEditorWidget, ITextChange
-} from '../../../../lib/notebook/cells/editor';
+} from '../../../lib/notebook/cells/editor';
 
 import {
-  CompletionWidget, CellCompletionHandler, CompletionModel, ICompletionPatch
-} from '../../../../lib/notebook/completion';
+  CompleterWidget, CellCompleterHandler, CompleterModel, ICompletionPatch
+} from '../../../lib/completer';
 
 import {
   CodeMirrorCodeCellWidgetRenderer
-} from '../../../../lib/notebook/codemirror/cells/widget';
+} from '../../../lib/notebook/codemirror/cells/widget';
 
 
-class TestCompletionModel extends CompletionModel {
+class TestCompleterModel extends CompleterModel {
   methods: string[] = [];
 
   createPatch(patch: string): ICompletionPatch {
@@ -43,7 +43,7 @@ class TestCompletionModel extends CompletionModel {
 }
 
 
-class TestCompletionHandler extends CellCompletionHandler {
+class TestCompleterHandler extends CellCompleterHandler {
   methods: string[] = [];
 
   makeRequest(request: ICompletionRequest): Promise<void> {
@@ -67,22 +67,22 @@ class TestCompletionHandler extends CellCompletionHandler {
     this.methods.push('onCompletionRequested');
   }
 
-  onCompletionSelected(widget: CompletionWidget, value: string): void {
+  onCompletionSelected(widget: CompleterWidget, value: string): void {
     super.onCompletionSelected(widget, value);
     this.methods.push('onCompletionSelected');
   }
 }
 
 
-describe('notebook/completion/handler', () => {
+describe('completer/handler', () => {
 
-  describe('CellCompletionHandler', () => {
+  describe('CellCompleterHandler', () => {
 
     describe('#constructor()', () => {
 
-      it('should create a completion handler', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
-        expect(handler).to.be.a(CellCompletionHandler);
+      it('should create a completer handler', () => {
+        let handler = new CellCompleterHandler(new CompleterWidget());
+        expect(handler).to.be.a(CellCompleterHandler);
       });
 
     });
@@ -90,12 +90,12 @@ describe('notebook/completion/handler', () => {
     describe('#kernel', () => {
 
       it('should default to null', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         expect(handler.kernel).to.be(null);
       });
 
       it('should be settable', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         let kernel = new MockKernel();
         expect(handler.kernel).to.be(null);
         handler.kernel = kernel;
@@ -109,13 +109,15 @@ describe('notebook/completion/handler', () => {
     describe('#activeCell', () => {
 
       it('should default to null', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         expect(handler.activeCell).to.be(null);
       });
 
       it('should be settable', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
-        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
+        let handler = new CellCompleterHandler(new CompleterWidget());
+        let cell = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(handler.activeCell).to.be(null);
         handler.activeCell = cell;
         expect(handler.activeCell).to.be.a(BaseCellWidget);
@@ -123,9 +125,13 @@ describe('notebook/completion/handler', () => {
       });
 
       it('should be resettable', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
-        let one = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
-        let two = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
+        let handler = new CellCompleterHandler(new CompleterWidget());
+        let one = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
+        let two = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
         expect(handler.activeCell).to.be(null);
         handler.activeCell = one;
         expect(handler.activeCell).to.be.a(BaseCellWidget);
@@ -140,7 +146,7 @@ describe('notebook/completion/handler', () => {
     describe('#isDisposed', () => {
 
       it('should be true if handler has been disposed', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         expect(handler.isDisposed).to.be(false);
         handler.dispose();
         expect(handler.isDisposed).to.be(true);
@@ -151,7 +157,7 @@ describe('notebook/completion/handler', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the handler resources', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         let kernel = new MockKernel();
         handler.kernel = kernel;
         expect(handler.isDisposed).to.be(false);
@@ -162,7 +168,7 @@ describe('notebook/completion/handler', () => {
       });
 
       it('should be safe to call multiple times', () => {
-        let handler = new CellCompletionHandler(new CompletionWidget());
+        let handler = new CellCompleterHandler(new CompleterWidget());
         expect(handler.isDisposed).to.be(false);
         handler.dispose();
         handler.dispose();
@@ -174,7 +180,7 @@ describe('notebook/completion/handler', () => {
     describe('#makeRequest()', () => {
 
       it('should reject if handler has no kernel', (done) => {
-        let handler = new TestCompletionHandler(new CompletionWidget());
+        let handler = new TestCompleterHandler(new CompleterWidget());
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -193,7 +199,7 @@ describe('notebook/completion/handler', () => {
       // TODO: This test needs to be fixed when MockKernel is updated.
       it('should resolve if handler has a kernel', () => {
         console.warn('This test needs to be fixed when MockKernel is updated.');
-        let handler = new TestCompletionHandler(new CompletionWidget());
+        let handler = new TestCompleterHandler(new CompleterWidget());
         let kernel = new MockKernel();
         let request: ICompletionRequest = {
           ch: 0,
@@ -213,27 +219,27 @@ describe('notebook/completion/handler', () => {
     describe('#onReply()', () => {
 
       it('should do nothing if handler has been disposed', () => {
-        let completion = new CompletionWidget();
-        let handler = new TestCompletionHandler(completion);
-        completion.model = new CompletionModel();
-        completion.model.options = ['foo', 'bar', 'baz'];
+        let completer = new CompleterWidget();
+        let handler = new TestCompleterHandler(completer);
+        completer.model = new CompleterModel();
+        completer.model.options = ['foo', 'bar', 'baz'];
         handler.dispose();
         handler.onReply(0, null, null);
-        expect(completion.model).to.be.ok();
+        expect(completer.model).to.be.ok();
       });
 
       it('should do nothing if pending request ID does not match', () => {
-        let completion = new CompletionWidget();
-        let handler = new TestCompletionHandler(completion);
-        completion.model = new CompletionModel();
-        completion.model.options = ['foo', 'bar', 'baz'];
+        let completer = new CompleterWidget();
+        let handler = new TestCompleterHandler(completer);
+        completer.model = new CompleterModel();
+        completer.model.options = ['foo', 'bar', 'baz'];
         handler.onReply(2, null, null);
-        expect(completion.model).to.be.ok();
+        expect(completer.model).to.be.ok();
       });
 
       it('should reset model if status is not ok', () => {
-        let completion = new CompletionWidget();
-        let handler = new TestCompletionHandler(completion);
+        let completer = new CompleterWidget();
+        let handler = new TestCompleterHandler(completer);
         let options = ['a', 'b', 'c'];
         let request: ICompletionRequest = {
           ch: 0,
@@ -258,16 +264,16 @@ describe('notebook/completion/handler', () => {
             matches: ['foo']
           }
         };
-        completion.model = new CompletionModel();
-        completion.model.options = options;
-        expect(completion.model.options).to.eql(options);
+        completer.model = new CompleterModel();
+        completer.model.options = options;
+        expect(completer.model.options).to.eql(options);
         handler.onReply(0, request, reply);
-        expect(completion.model.options).to.be(null);
+        expect(completer.model.options).to.be(null);
       });
 
       it('should update model if status is ok', () => {
-        let completion = new CompletionWidget();
-        let handler = new TestCompletionHandler(completion);
+        let completer = new CompleterWidget();
+        let handler = new TestCompleterHandler(completer);
         let options = ['a', 'b', 'c'];
         let request: ICompletionRequest = {
           ch: 0,
@@ -292,11 +298,11 @@ describe('notebook/completion/handler', () => {
             matches: ['foo']
           }
         };
-        completion.model = new CompletionModel();
-        completion.model.options = options;
-        expect(completion.model.options).to.eql(options);
+        completer.model = new CompleterModel();
+        completer.model.options = options;
+        expect(completer.model.options).to.eql(options);
         handler.onReply(0, request, reply);
-        expect(completion.model.options).to.eql(reply.content.matches);
+        expect(completer.model.options).to.eql(reply.content.matches);
       });
 
     });
@@ -304,7 +310,7 @@ describe('notebook/completion/handler', () => {
     describe('#onTextChanged()', () => {
 
       it('should fire when the active editor emits a text change', () => {
-        let handler = new TestCompletionHandler(new CompletionWidget());
+        let handler = new TestCompleterHandler(new CompleterWidget());
         let change: ITextChange = {
           ch: 0,
           chHeight: 0,
@@ -315,7 +321,9 @@ describe('notebook/completion/handler', () => {
           oldValue: 'fo',
           newValue: 'foo'
         };
-        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
+        let cell = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
 
         handler.activeCell = cell;
         expect(handler.methods).to.not.contain('onTextChanged');
@@ -324,10 +332,10 @@ describe('notebook/completion/handler', () => {
       });
 
       it('should call model change handler if model exists', () => {
-        let completion = new CompletionWidget({
-          model: new TestCompletionModel()
+        let completer = new CompleterWidget({
+          model: new TestCompleterModel()
         });
-        let handler = new TestCompletionHandler(completion);
+        let handler = new TestCompleterHandler(completer);
         let change: ITextChange = {
           ch: 0,
           chHeight: 0,
@@ -338,8 +346,10 @@ describe('notebook/completion/handler', () => {
           oldValue: 'fo',
           newValue: 'foo'
         };
-        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
-        let model = completion.model as TestCompletionModel;
+        let cell = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
+        let model = completer.model as TestCompleterModel;
 
         handler.activeCell = cell;
         expect(model.methods).to.not.contain('handleTextChange');
@@ -352,7 +362,7 @@ describe('notebook/completion/handler', () => {
     describe('#onCompletionRequested()', () => {
 
       it('should fire when the active editor emits a request', () => {
-        let handler = new TestCompletionHandler(new CompletionWidget());
+        let handler = new TestCompleterHandler(new CompleterWidget());
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -362,7 +372,9 @@ describe('notebook/completion/handler', () => {
           position: 0,
           currentValue: 'foo'
         };
-        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
+        let cell = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
 
         handler.activeCell = cell;
         expect(handler.methods).to.not.contain('onCompletionRequested');
@@ -371,10 +383,10 @@ describe('notebook/completion/handler', () => {
       });
 
       it('should make a kernel request if kernel and model exist', () => {
-        let completion = new CompletionWidget({
-          model: new TestCompletionModel()
+        let completer = new CompleterWidget({
+          model: new TestCompleterModel()
         });
-        let handler = new TestCompletionHandler(completion);
+        let handler = new TestCompleterHandler(completer);
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -384,8 +396,9 @@ describe('notebook/completion/handler', () => {
           position: 0,
           currentValue: 'foo'
         };
-        let cell = new BaseCellWidget({renderer:CodeMirrorCodeCellWidgetRenderer.defaultRenderer});
-        let model = completion.model as TestCompletionModel;
+        let cell = new BaseCellWidget({
+          renderer: CodeMirrorCodeCellWidgetRenderer.defaultRenderer
+        });
 
         handler.kernel = new MockKernel();
         handler.activeCell = cell;
@@ -398,34 +411,34 @@ describe('notebook/completion/handler', () => {
 
     describe('#onCompletionSelected()', () => {
 
-      it('should fire when the completion widget emits a signal', () => {
-        let completion = new CompletionWidget();
-        let handler = new TestCompletionHandler(completion);
+      it('should fire when the completer widget emits a signal', () => {
+        let completer = new CompleterWidget();
+        let handler = new TestCompleterHandler(completer);
 
         expect(handler.methods).to.not.contain('onCompletionSelected');
-        completion.selected.emit('foo');
+        completer.selected.emit('foo');
         expect(handler.methods).to.contain('onCompletionSelected');
       });
 
       it('should call model create patch method if model exists', () => {
-        let completion = new CompletionWidget({
-          model: new TestCompletionModel()
+        let completer = new CompleterWidget({
+          model: new TestCompleterModel()
         });
-        let handler = new TestCompletionHandler(completion);
-        let model = completion.model as TestCompletionModel;
+        let handler = new TestCompleterHandler(completer);
+        let model = completer.model as TestCompleterModel;
         let renderer = CodeMirrorCodeCellWidgetRenderer.defaultRenderer;
 
         handler.activeCell = new BaseCellWidget({ renderer });
         expect(model.methods).to.not.contain('createPatch');
-        completion.selected.emit('foo');
+        completer.selected.emit('foo');
         expect(model.methods).to.contain('createPatch');
       });
 
       it('should update cell if patch exists', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let patch = 'foobar';
-        let completion = new CompletionWidget({ model });
-        let handler = new TestCompletionHandler(completion);
+        let completer = new CompleterWidget({ model });
+        let handler = new TestCompleterHandler(completer);
         let renderer = CodeMirrorCodeCellWidgetRenderer.defaultRenderer;
         let cell = new BaseCellWidget({ renderer });
         let request: ICompletionRequest = {
@@ -443,7 +456,7 @@ describe('notebook/completion/handler', () => {
         handler.activeCell.model.source = request.currentValue;
         model.original = request;
         model.cursor = { start: 0, end: 3 };
-        completion.selected.emit(patch);
+        completer.selected.emit(patch);
         expect(handler.activeCell.model.source).to.equal(patch);
       });
 

+ 40 - 40
test/src/notebook/completion/model.spec.ts → test/src/completer/model.spec.ts

@@ -4,23 +4,23 @@
 import expect = require('expect.js');
 
 import {
-  CompletionModel, ICursorSpan, ICompletionItem, ICompletionPatch
-} from '../../../../lib/notebook/completion';
+  CompleterModel, ICursorSpan, ICompleterItem, ICompletionPatch
+} from '../../../lib/completer';
 
 import {
   ICompletionRequest, ICoords, ITextChange
-} from '../../../../lib/notebook/cells/editor';
+} from '../../../lib/notebook/cells/editor';
 
 
-describe('notebook/completion/model', () => {
+describe('completer/model', () => {
 
-  describe('CompletionModel', () => {
+  describe('CompleterModel', () => {
 
     describe('#constructor()', () => {
 
-      it('should create a completion model', () => {
-        let model = new CompletionModel();
-        expect(model).to.be.a(CompletionModel);
+      it('should create a completer model', () => {
+        let model = new CompleterModel();
+        expect(model).to.be.a(CompleterModel);
       });
 
     });
@@ -28,7 +28,7 @@ describe('notebook/completion/model', () => {
     describe('#stateChanged', () => {
 
       it('should signal when model options have changed', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let listener = (sender: any, args: void) => { called++; };
         model.stateChanged.connect(listener);
@@ -40,7 +40,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should not signal when options have not changed', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let listener = (sender: any, args: void) => { called++; };
         model.stateChanged.connect(listener);
@@ -54,7 +54,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should signal when original request changes', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let request: ICompletionRequest = {
           ch: 0,
@@ -75,7 +75,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should not signal when original request has not changed', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let request: ICompletionRequest = {
           ch: 0,
@@ -98,7 +98,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should signal when current text changes', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let currentValue = 'foo';
         let oldValue = currentValue;
@@ -135,7 +135,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should not signal when current text has not change', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let called = 0;
         let currentValue = 'foo';
         let oldValue = currentValue;
@@ -178,8 +178,8 @@ describe('notebook/completion/model', () => {
     describe('#items', () => {
 
       it('should return an unfiltered list of items if query is blank', () => {
-        let model = new CompletionModel();
-        let want: ICompletionItem[] = [
+        let model = new CompleterModel();
+        let want: ICompleterItem[] = [
           { raw: 'foo', text: 'foo' },
           { raw: 'bar', text: 'bar' },
           { raw: 'baz', text: 'baz' }
@@ -189,8 +189,8 @@ describe('notebook/completion/model', () => {
       });
 
       it('should return a filtered list of items if query is set', () => {
-        let model = new CompletionModel();
-        let want: ICompletionItem[] = [
+        let model = new CompleterModel();
+        let want: ICompleterItem[] = [
           { raw: 'foo', text: '<mark>f</mark>oo' }
         ];
         model.options = ['foo', 'bar', 'baz'];
@@ -199,8 +199,8 @@ describe('notebook/completion/model', () => {
       });
 
       it('should order list based on score', () => {
-        let model = new CompletionModel();
-        let want: ICompletionItem[] = [
+        let model = new CompleterModel();
+        let want: ICompleterItem[] = [
           { raw: 'qux', text: '<mark>qux</mark>' },
           { raw: 'quux', text: '<mark>qu</mark>u<mark>x</mark>' }
         ];
@@ -210,8 +210,8 @@ describe('notebook/completion/model', () => {
       });
 
       it('should break ties in score by locale sort', () => {
-        let model = new CompletionModel();
-        let want: ICompletionItem[] = [
+        let model = new CompleterModel();
+        let want: ICompleterItem[] = [
           { raw: 'quux', text: '<mark>qu</mark>ux' },
           { raw: 'qux', text: '<mark>qu</mark>x' }
         ];
@@ -225,12 +225,12 @@ describe('notebook/completion/model', () => {
     describe('#options', () => {
 
       it('should default to null', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.options).to.be(null);
       });
 
       it('should return model options', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let options = ['foo'];
         model.options = options;
         expect(model.options).to.not.equal(options);
@@ -242,12 +242,12 @@ describe('notebook/completion/model', () => {
     describe('#original', () => {
 
       it('should default to null', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.original).to.be(null);
       });
 
       it('should return the original request', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -266,12 +266,12 @@ describe('notebook/completion/model', () => {
     describe('#current', () => {
 
       it('should default to null', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.current).to.be(null);
       });
 
       it('should not set if original request is nonexistent', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo';
         let oldValue = currentValue;
         let newValue = 'foob';
@@ -303,7 +303,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should not set if cursor is nonexistent', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo';
         let oldValue = currentValue;
         let newValue = 'foob';
@@ -335,7 +335,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should reset model if change is shorter than original', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo';
         let oldValue = currentValue;
         let newValue = 'fo';
@@ -371,12 +371,12 @@ describe('notebook/completion/model', () => {
     describe('#cursor', () => {
 
       it('should default to null', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.cursor).to.be(null);
       });
 
       it('should not set if original request is nonexistent', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let cursor: ICursorSpan = { start: 0, end: 0 };
         let request: ICompletionRequest = {
           ch: 0,
@@ -399,7 +399,7 @@ describe('notebook/completion/model', () => {
     describe('#isDisposed', () => {
 
       it('should be true if model has been disposed', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.isDisposed).to.be(false);
         model.dispose();
         expect(model.isDisposed).to.be(true);
@@ -410,7 +410,7 @@ describe('notebook/completion/model', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the model resources', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         model.options = ['foo'];
         expect(model.isDisposed).to.be(false);
         expect(model.options).to.be.ok();
@@ -420,7 +420,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should be safe to call multiple times', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.isDisposed).to.be(false);
         model.dispose();
         model.dispose();
@@ -432,7 +432,7 @@ describe('notebook/completion/model', () => {
     describe('#handleTextChange()', () => {
 
       it('should set current change value', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo';
         let oldValue = currentValue;
         let newValue = 'foob';
@@ -463,7 +463,7 @@ describe('notebook/completion/model', () => {
       });
 
       it('should reset model if last character of change is whitespace', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo';
         let oldValue = currentValue;
         let newValue = 'foo ';
@@ -496,7 +496,7 @@ describe('notebook/completion/model', () => {
     describe('#createPatch()', () => {
 
       it('should return a patch value', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let patch = 'foobar';
         let want: ICompletionPatch = { text: patch, position: patch.length };
         let cursor: ICursorSpan = { start: 0, end: 3 };
@@ -515,12 +515,12 @@ describe('notebook/completion/model', () => {
       });
 
       it('should return null if original request or cursor are null', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         expect(model.createPatch('foo')).to.be(null);
       });
 
       it('should handle line breaks in original value', () => {
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let currentValue = 'foo\nbar';
         let patch = 'barbaz';
         let want: ICompletionPatch = { text: 'foo\nbarbaz', position: 10 };

+ 120 - 115
test/src/notebook/completion/widget.spec.ts → test/src/completer/widget.spec.ts

@@ -17,24 +17,24 @@ import {
 
 import {
   ICompletionRequest, ICoords
-} from '../../../../lib/notebook/cells/editor';
+} from '../../../lib/notebook/cells/editor';
 
 import {
-  CompletionWidget, CompletionModel, ICompletionItem
-} from '../../../../lib/notebook/completion';
+  CompleterWidget, CompleterModel, ICompleterItem
+} from '../../../lib/completer';
 
 
 const TEST_ITEM_CLASS = 'jp-TestItem';
 
-const ITEM_CLASS = 'jp-Completion-item';
+const ITEM_CLASS = 'jp-Completer-item';
 
 const ACTIVE_CLASS = 'jp-mod-active';
 
-const MAX_HEIGHT = 250;
+const MAX_HEIGHT = 200;
 
 
-class CustomRenderer extends CompletionWidget.Renderer {
-  createItemNode(item: ICompletionItem): HTMLLIElement {
+class CustomRenderer extends CompleterWidget.Renderer {
+  createItemNode(item: ICompleterItem): HTMLLIElement {
     let li = super.createItemNode(item);
     li.classList.add(TEST_ITEM_CLASS);
     return li;
@@ -42,7 +42,7 @@ class CustomRenderer extends CompletionWidget.Renderer {
 }
 
 
-class LogWidget extends CompletionWidget {
+class LogWidget extends CompleterWidget {
   events: string[] = [];
 
   methods: string[] = [];
@@ -64,36 +64,37 @@ class LogWidget extends CompletionWidget {
 }
 
 
-describe('notebook/completion/widget', () => {
+describe('completer/widget', () => {
 
-  describe('CompletionWidget', () => {
+  describe('CompleterWidget', () => {
 
     describe('#constructor()', () => {
 
-      it('should create a completion widget', () => {
-        let widget = new CompletionWidget();
-        expect(widget).to.be.a(CompletionWidget);
-        expect(widget.node.classList).to.contain('jp-Completion');
+      it('should create a completer widget', () => {
+        let widget = new CompleterWidget();
+        expect(widget).to.be.a(CompleterWidget);
+        expect(widget.node.classList).to.contain('jp-Completer');
       });
 
       it('should accept options with a model', () => {
-        let options: CompletionWidget.IOptions = {
-          model: new CompletionModel()
+        let options: CompleterWidget.IOptions = {
+          model: new CompleterModel()
         };
-        let widget = new CompletionWidget(options);
-        expect(widget).to.be.a(CompletionWidget);
+        let widget = new CompleterWidget(options);
+        expect(widget).to.be.a(CompleterWidget);
         expect(widget.model).to.equal(options.model);
       });
 
       it('should accept options with a renderer', () => {
-        let options: CompletionWidget.IOptions = {
-          model: new CompletionModel(),
+        let options: CompleterWidget.IOptions = {
+          anchor: document.createElement('div'),
+          model: new CompleterModel(),
           renderer: new CustomRenderer()
         };
         options.model.options = ['foo', 'bar'];
 
-        let widget = new CompletionWidget(options);
-        expect(widget).to.be.a(CompletionWidget);
+        let widget = new CompleterWidget(options);
+        expect(widget).to.be.a(CompleterWidget);
         sendMessage(widget, WidgetMessage.UpdateRequest);
 
         let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
@@ -107,8 +108,8 @@ describe('notebook/completion/widget', () => {
 
       it('should emit a signal when an item is selected', () => {
         let anchor = new Widget();
-        let options: CompletionWidget.IOptions = {
-          model: new CompletionModel(),
+        let options: CompleterWidget.IOptions = {
+          model: new CompleterModel(),
           anchor: anchor.node
         };
         let value = '';
@@ -116,7 +117,7 @@ describe('notebook/completion/widget', () => {
         options.model.options = ['foo', 'bar'];
         Widget.attach(anchor, document.body);
 
-        let widget = new CompletionWidget(options);
+        let widget = new CompleterWidget(options);
 
         widget.selected.connect(listener);
         Widget.attach(widget, document.body);
@@ -132,10 +133,10 @@ describe('notebook/completion/widget', () => {
 
     describe('#visibilityChanged', () => {
 
-      it('should emit a signal when completion visibility changes', () => {
+      it('should emit a signal when completer visibility changes', () => {
         let anchor = new Widget();
-        let options: CompletionWidget.IOptions = {
-          model: new CompletionModel(),
+        let options: CompleterWidget.IOptions = {
+          model: new CompleterModel(),
           anchor: anchor.node
         };
         let called = false;
@@ -143,7 +144,7 @@ describe('notebook/completion/widget', () => {
         options.model.options = ['foo', 'bar'];
         Widget.attach(anchor, document.body);
 
-        let widget = new CompletionWidget(options);
+        let widget = new CompleterWidget(options);
 
         widget.visibilityChanged.connect(listener);
         expect(called).to.be(false);
@@ -159,28 +160,28 @@ describe('notebook/completion/widget', () => {
     describe('#model', () => {
 
       it('should default to null', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         expect(widget.model).to.be(null);
       });
 
       it('should be settable', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         expect(widget.model).to.be(null);
-        widget.model = new CompletionModel();
-        expect(widget.model).to.be.a(CompletionModel);
+        widget.model = new CompleterModel();
+        expect(widget.model).to.be.a(CompleterModel);
       });
 
       it('should be safe to set multiple times', () => {
-        let model = new CompletionModel();
-        let widget = new CompletionWidget();
+        let model = new CompleterModel();
+        let widget = new CompleterWidget();
         widget.model = model;
         widget.model = model;
         expect(widget.model).to.be(model);
       });
 
       it('should be safe to reset', () => {
-        let model = new CompletionModel();
-        let widget = new CompletionWidget({ model: new CompletionModel() });
+        let model = new CompleterModel();
+        let widget = new CompleterWidget({ model: new CompleterModel() });
         expect(widget.model).not.to.be(model);
         widget.model = model;
         expect(widget.model).to.be(model);
@@ -191,12 +192,12 @@ describe('notebook/completion/widget', () => {
     describe('#anchor', () => {
 
       it('should default to null', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         expect(widget.anchor).to.be(null);
       });
 
       it('should be settable', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         expect(widget.anchor).to.be(null);
         widget.anchor = new Widget().node;
         expect(widget.anchor).to.be.a(Node);
@@ -204,7 +205,7 @@ describe('notebook/completion/widget', () => {
 
       it('should be safe to reset', () => {
         let anchor = new Widget();
-        let widget = new CompletionWidget({ anchor: (new Widget()).node });
+        let widget = new CompleterWidget({ anchor: (new Widget()).node });
         expect(widget.anchor).not.to.be(anchor.node);
         widget.anchor = anchor.node;
         expect(widget.anchor).to.be(anchor.node);
@@ -215,13 +216,13 @@ describe('notebook/completion/widget', () => {
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the widget', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
       });
 
       it('should be safe to call multiple times', () => {
-        let widget = new CompletionWidget();
+        let widget = new CompleterWidget();
         widget.dispose();
         widget.dispose();
         expect(widget.isDisposed).to.be(true);
@@ -231,16 +232,16 @@ describe('notebook/completion/widget', () => {
 
     describe('#reset()', () => {
 
-      it('should reset the completion widget', () => {
+      it('should reset the completer widget', () => {
         let anchor = new Widget();
-        let model = new CompletionModel();
-        let options: CompletionWidget.IOptions = {
+        let model = new CompleterModel();
+        let options: CompleterWidget.IOptions = {
           model, anchor: anchor.node
         };
         model.options = ['foo', 'bar'];
         Widget.attach(anchor, document.body);
 
-        let widget = new CompletionWidget(options);
+        let widget = new CompleterWidget(options);
 
         Widget.attach(widget, document.body);
         sendMessage(widget, WidgetMessage.UpdateRequest);
@@ -281,14 +282,14 @@ describe('notebook/completion/widget', () => {
 
         it('should reset if keydown is outside anchor', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           model.options = ['foo', 'bar'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           Widget.attach(widget, document.body);
           sendMessage(widget, WidgetMessage.UpdateRequest);
@@ -304,8 +305,8 @@ describe('notebook/completion/widget', () => {
 
         it('should trigger a selected signal on enter key', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
@@ -315,7 +316,7 @@ describe('notebook/completion/widget', () => {
           model.options = ['foo', 'bar', 'baz'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
@@ -329,14 +330,14 @@ describe('notebook/completion/widget', () => {
 
         it('should select the item below and cycle back on down', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           model.options = ['foo', 'bar', 'baz'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
           let target = document.createElement('div');
 
           anchor.node.appendChild(target);
@@ -366,14 +367,14 @@ describe('notebook/completion/widget', () => {
 
         it('should select the item above and cycle back on up', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           model.options = ['foo', 'bar', 'baz'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           Widget.attach(widget, document.body);
           sendMessage(widget, WidgetMessage.UpdateRequest);
@@ -399,40 +400,39 @@ describe('notebook/completion/widget', () => {
           anchor.dispose();
         });
 
-        it('should mark common subset on tab and select on next tab', () => {
+        it('should mark common subset on start and select on tab', (done) => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
           let listener = (sender: any, selected: string) => {
             value = selected;
           };
-          model.options = ['foo', 'four', 'foz'];
+          model.options = ['fo', 'foo', 'foo', 'fooo'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
           sendMessage(widget, WidgetMessage.UpdateRequest);
-
-          let marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
-          expect(marked).to.be.empty();
-          expect(value).to.be('');
-          simulate(anchor.node, 'keydown', { keyCode: 9 });  // Tab
-          sendMessage(widget, WidgetMessage.UpdateRequest);
-          marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
-          expect(value).to.be('fo');
-          expect(marked).to.have.length(3);
-          expect(marked[0].textContent).to.be('fo');
-          expect(marked[1].textContent).to.be('fo');
-          expect(marked[2].textContent).to.be('fo');
-          simulate(anchor.node, 'keydown', { keyCode: 9 });  // Tab
-          expect(value).to.be('foo');
-          widget.dispose();
-          anchor.dispose();
+          requestAnimationFrame(() => {
+            let marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
+            expect(value).to.be('fo');
+            expect(marked).to.have.length(4);
+            expect(marked[0].textContent).to.be('fo');
+            expect(marked[1].textContent).to.be('fo');
+            expect(marked[2].textContent).to.be('fo');
+            expect(marked[3].textContent).to.be('fo');
+            simulate(anchor.node, 'keydown', { keyCode: 9 });  // Tab key
+            sendMessage(widget, WidgetMessage.UpdateRequest);
+            expect(value).to.be('fo');
+            widget.dispose();
+            anchor.dispose();
+            done();
+          });
         });
 
       });
@@ -441,8 +441,8 @@ describe('notebook/completion/widget', () => {
 
         it('should trigger a selected signal on mouse down', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
@@ -453,7 +453,7 @@ describe('notebook/completion/widget', () => {
           model.query = 'b';
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
@@ -461,7 +461,7 @@ describe('notebook/completion/widget', () => {
 
           let item = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`)[1];
 
-          expect(value).to.be('');
+          expect(value).to.be('ba');
           simulate(item, 'mousedown');
           expect(value).to.be('baz');
           widget.dispose();
@@ -470,8 +470,8 @@ describe('notebook/completion/widget', () => {
 
         it('should ignore nonstandard mouse clicks (e.g., right click)', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
@@ -481,7 +481,7 @@ describe('notebook/completion/widget', () => {
           model.options = ['foo', 'bar'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
@@ -495,8 +495,8 @@ describe('notebook/completion/widget', () => {
 
         it('should ignore a mouse down that misses an item', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
@@ -506,7 +506,7 @@ describe('notebook/completion/widget', () => {
           model.options = ['foo', 'bar'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
@@ -520,8 +520,8 @@ describe('notebook/completion/widget', () => {
 
         it('should hide widget if mouse down misses it', () => {
           let anchor = new Widget();
-          let model = new CompletionModel();
-          let options: CompletionWidget.IOptions = {
+          let model = new CompleterModel();
+          let options: CompleterWidget.IOptions = {
             model, anchor: anchor.node
           };
           let value = '';
@@ -531,7 +531,7 @@ describe('notebook/completion/widget', () => {
           model.options = ['foo', 'bar'];
           Widget.attach(anchor, document.body);
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget(options);
 
           widget.selected.connect(listener);
           Widget.attach(widget, document.body);
@@ -548,11 +548,11 @@ describe('notebook/completion/widget', () => {
 
       context('scroll', () => {
 
-        it('should move along with the pegged anchor', (done) => {
+        it('should position itself according to the anchor', (done) => {
           let anchor = document.createElement('div');
-          let container = new Widget();
-          let model = new CompletionModel();
-          let coords: ICoords = { left: 0, right: 0, top: 500, bottom: 0 };
+          let content = new Widget();
+          let model = new CompleterModel();
+          let coords: ICoords = { left: 0, right: 0, top: 100, bottom: 120 };
           let request: ICompletionRequest = {
             ch: 0,
             chHeight: 0,
@@ -562,33 +562,38 @@ describe('notebook/completion/widget', () => {
             position: 0,
             currentValue: 'f'
           };
-          let options: CompletionWidget.IOptions = { model, anchor: anchor };
+
+          content.node.style.height = '5000px';
+          content.node.style.width = '400px';
+          content.node.style.overflow = 'auto';
+          content.node.style.background = 'yellow';
+
+          anchor.style.background = 'red';
+          anchor.style.height = '2000px';
+          anchor.style.width = '500px';
+          anchor.style.maxHeight = '500px';
+          anchor.style.overflow = 'hidden';
 
           document.body.appendChild(anchor);
-          anchor.style.height = '1000px';
-          anchor.style.overflow = 'auto';
+          Widget.attach(content, anchor);
 
-          Widget.attach(container, anchor);
-          container.node.style.height = '5000px';
-          anchor.scrollTop = 0;
+          anchor.scrollTop = 100;
           model.original = request;
+          model.cursor = { start: 0, end: 0 };
           model.options = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
 
-          let widget = new CompletionWidget(options);
+          let widget = new CompleterWidget({ model, anchor: anchor });
           Widget.attach(widget, document.body);
           sendMessage(widget, WidgetMessage.UpdateRequest);
 
-          let top = parseInt(window.getComputedStyle(widget.node).top, 10);
-          let offset = 200;
-          expect(top).to.be(coords.top - MAX_HEIGHT);
-          anchor.scrollTop = offset;
           simulate(anchor, 'scroll');
 
           requestAnimationFrame(() => {
             let top = parseInt(window.getComputedStyle(widget.node).top, 10);
-            expect(top).to.be(coords.top - MAX_HEIGHT - offset);
+            expect(top).to.be(coords.bottom);
             widget.dispose();
-            container.dispose();
+            content.dispose();
+            document.body.removeChild(anchor);
             done();
           });
         });
@@ -601,7 +606,7 @@ describe('notebook/completion/widget', () => {
 
       it('should emit a selection if there is only one match', () => {
         let anchor = new Widget();
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -612,14 +617,14 @@ describe('notebook/completion/widget', () => {
           currentValue: 'f'
         };
         let value = '';
-        let options: CompletionWidget.IOptions = { model, anchor: anchor.node };
+        let options: CompleterWidget.IOptions = { model, anchor: anchor.node };
         let listener = (sender: any, selected: string) => { value = selected; };
 
         Widget.attach(anchor, document.body);
         model.original = request;
         model.options = ['foo'];
 
-        let widget = new CompletionWidget(options);
+        let widget = new CompleterWidget(options);
         widget.selected.connect(listener);
         Widget.attach(widget, document.body);
 
@@ -638,7 +643,7 @@ describe('notebook/completion/widget', () => {
 
       it('should un-hide widget if multiple options are available', () => {
         let anchor = new Widget();
-        let model = new CompletionModel();
+        let model = new CompleterModel();
         let request: ICompletionRequest = {
           ch: 0,
           chHeight: 0,
@@ -648,13 +653,13 @@ describe('notebook/completion/widget', () => {
           position: 0,
           currentValue: 'f'
         };
-        let options: CompletionWidget.IOptions = { model, anchor: anchor.node };
+        let options: CompleterWidget.IOptions = { model, anchor: anchor.node };
 
         Widget.attach(anchor, document.body);
         model.original = request;
         model.options = ['foo', 'bar', 'baz'];
 
-        let widget = new CompletionWidget(options);
+        let widget = new CompleterWidget(options);
         widget.hide();
         expect(widget.isHidden).to.be(true);
         Widget.attach(widget, document.body);

+ 4 - 4
test/src/index.ts

@@ -4,6 +4,10 @@
 import './common/activitymonitor.spec';
 import './common/observablelist.spec';
 
+import './completer/handler.spec';
+import './completer/model.spec';
+import './completer/widget.spec';
+
 import './console/history.spec';
 
 import './dialog/dialog.spec';
@@ -24,10 +28,6 @@ import './notebook/cells/editor.spec';
 import './notebook/cells/model.spec';
 import './notebook/cells/widget.spec';
 
-import './notebook/completion/handler.spec';
-import './notebook/completion/model.spec';
-import './notebook/completion/widget.spec';
-
 import './notebook/notebook/actions.spec';
 import './notebook/notebook/default-toolbar.spec';
 import './notebook/notebook/model.spec';

+ 5 - 5
test/src/notebook/notebook/panel.spec.ts

@@ -20,8 +20,8 @@ import {
 } from '../../../../lib/docregistry';
 
 import {
-  CompletionWidget
-} from '../../../../lib/notebook/completion';
+  CompleterWidget
+} from '../../../../lib/completer';
 
 import {
   INotebookModel, NotebookModel
@@ -413,11 +413,11 @@ describe('notebook/notebook/panel', () => {
 
       });
 
-      describe('#createCompletion()', () => {
+      describe('#createCompleter()', () => {
 
-        it('should create a completion widget', () => {
+        it('should create a completer widget', () => {
           let renderer = new CodeMirrorNotebookPanelRenderer();
-          expect(renderer.createCompletion()).to.be.a(CompletionWidget);
+          expect(renderer.createCompleter()).to.be.a(CompleterWidget);
         });
 
       });