browser/devtools/sourceeditor/autocomplete.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* vim:set ts=2 sw=2 sts=2 et tw=80:
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 const cssAutoCompleter = require("devtools/sourceeditor/css-autocompleter");
     7 const { AutocompletePopup } = require("devtools/shared/autocomplete-popup");
     9 const privates = new WeakMap();
    11 /**
    12  * Prepares an editor instance for autocompletion, setting up the popup and the
    13  * CSS completer instance.
    14  */
    15 function setupAutoCompletion(ctx, walker) {
    16   let { cm, ed, Editor } = ctx;
    18   let win = ed.container.contentWindow.wrappedJSObject;
    20   let completer = null;
    21   if (ed.config.mode == Editor.modes.css)
    22     completer = new cssAutoCompleter({walker: walker});
    24   let popup = new AutocompletePopup(win.parent.document, {
    25     position: "after_start",
    26     fixedWidth: true,
    27     theme: "auto",
    28     autoSelect: true
    29   });
    31   let cycle = (reverse) => {
    32     if (popup && popup.isOpen) {
    33       cycleSuggestions(ed, reverse == true);
    34       return;
    35     }
    37     return win.CodeMirror.Pass;
    38   };
    40   let keyMap = {
    41     "Tab": cycle,
    42     "Down": cycle,
    43     "Shift-Tab": cycle.bind(this, true),
    44     "Up": cycle.bind(this, true),
    45     "Enter": () => {
    46       if (popup && popup.isOpen) {
    47         if (!privates.get(ed).suggestionInsertedOnce) {
    48           privates.get(ed).insertingSuggestion = true;
    49           let {label, preLabel, text} = popup.getItemAtIndex(0);
    50           let cur = ed.getCursor();
    51           ed.replaceText(text.slice(preLabel.length), cur, cur);
    52         }
    53         popup.hidePopup();
    54         // This event is used in tests
    55         ed.emit("popup-hidden");
    56         return;
    57       }
    59       return win.CodeMirror.Pass;
    60     }
    61   };
    62   keyMap[Editor.accel("Space")] = cm => autoComplete(ctx);
    63   cm.addKeyMap(keyMap);
    65   cm.on("keydown", (cm, e) => onEditorKeypress(ctx, e));
    66   ed.on("change", () => autoComplete(ctx));
    67   ed.on("destroy", () => {
    68     cm.off("keydown", (cm, e) => onEditorKeypress(ctx, e));
    69     ed.off("change", () => autoComplete(ctx));
    70     popup.destroy();
    71     popup = null;
    72     completer = null;
    73   });
    75   privates.set(ed, {
    76     popup: popup,
    77     completer: completer,
    78     insertingSuggestion: false,
    79     suggestionInsertedOnce: false
    80   });
    81 }
    83 /**
    84  * Provides suggestions to autocomplete the current token/word being typed.
    85  */
    86 function autoComplete({ ed, cm }) {
    87   let private = privates.get(ed);
    88   let { completer, popup } = private;
    89   if (!completer || private.insertingSuggestion || private.doNotAutocomplete) {
    90     private.insertingSuggestion = false;
    91     return;
    92   }
    93   let cur = ed.getCursor();
    94   completer.complete(cm.getRange({line: 0, ch: 0}, cur), cur)
    95     .then(suggestions => {
    96     if (!suggestions || !suggestions.length || suggestions[0].preLabel == null) {
    97       private.suggestionInsertedOnce = false;
    98       popup.hidePopup();
    99       ed.emit("after-suggest");
   100       return;
   101     }
   102     // The cursor is at the end of the currently entered part of the token, like
   103     // "backgr|" but we need to open the popup at the beginning of the character
   104     // "b". Thus we need to calculate the width of the entered part of the token
   105     // ("backgr" here). 4 comes from the popup's left padding.
   107     let cursorElement = cm.display.cursorDiv.querySelector(".CodeMirror-cursor");
   108     let left = suggestions[0].preLabel.length * cm.defaultCharWidth() + 4;
   109     popup.hidePopup();
   110     popup.setItems(suggestions);
   111     popup.openPopup(cursorElement, -1 * left, 0);
   112     private.suggestionInsertedOnce = false;
   113     // This event is used in tests.
   114     ed.emit("after-suggest");
   115   });
   116 }
   118 /**
   119  * Cycles through provided suggestions by the popup in a top to bottom manner
   120  * when `reverse` is not true. Opposite otherwise.
   121  */
   122 function cycleSuggestions(ed, reverse) {
   123   let private = privates.get(ed);
   124   let { popup, completer } = private;
   125   let cur = ed.getCursor();
   126   private.insertingSuggestion = true;
   127   if (!private.suggestionInsertedOnce) {
   128     private.suggestionInsertedOnce = true;
   129     let firstItem;
   130     if (reverse) {
   131       firstItem = popup.getItemAtIndex(popup.itemCount - 1);
   132       popup.selectPreviousItem();
   133     } else {
   134       firstItem = popup.getItemAtIndex(0);
   135       if (firstItem.label == firstItem.preLabel && popup.itemCount > 1) {
   136         firstItem = popup.getItemAtIndex(1);
   137         popup.selectNextItem();
   138       }
   139     }
   140     if (popup.itemCount == 1)
   141       popup.hidePopup();
   142     ed.replaceText(firstItem.text.slice(firstItem.preLabel.length), cur, cur);
   143   } else {
   144     let fromCur = {
   145       line: cur.line,
   146       ch  : cur.ch - popup.selectedItem.text.length
   147     };
   148     if (reverse)
   149       popup.selectPreviousItem();
   150     else
   151       popup.selectNextItem();
   152     ed.replaceText(popup.selectedItem.text, fromCur, cur);
   153   }
   154   // This event is used in tests.
   155   ed.emit("suggestion-entered");
   156 }
   158 /**
   159  * onkeydown handler for the editor instance to prevent autocompleting on some
   160  * keypresses.
   161  */
   162 function onEditorKeypress({ ed, Editor }, event) {
   163   let private = privates.get(ed);
   165   // Do not try to autocomplete with multiple selections.
   166   if (ed.hasMultipleSelections()) {
   167     private.doNotAutocomplete = true;
   168     private.popup.hidePopup();
   169     return;
   170   }
   172   if ((event.ctrlKey || event.metaKey) && event.keyCode == event.DOM_VK_SPACE) {
   173     // When Ctrl/Cmd + Space is pressed, two simultaneous keypresses are emitted
   174     // first one for just the Ctrl/Cmd and second one for combo. The first one
   175     // leave the private.doNotAutocomplete as true, so we have to make it false
   176     private.doNotAutocomplete = false;
   177     return;
   178   }
   180   if (event.ctrlKey || event.metaKey || event.altKey) {
   181     private.doNotAutocomplete = true;
   182     private.popup.hidePopup();
   183     return;
   184   }
   186   switch (event.keyCode) {
   187     case event.DOM_VK_RETURN:
   188       private.doNotAutocomplete = true;
   189       break;
   191     case event.DOM_VK_ESCAPE:
   192       if (private.popup.isOpen)
   193         event.preventDefault();
   194     case event.DOM_VK_LEFT:
   195     case event.DOM_VK_RIGHT:
   196     case event.DOM_VK_HOME:
   197     case event.DOM_VK_END:
   198       private.doNotAutocomplete = true;
   199       private.popup.hidePopup();
   200       break;
   202     case event.DOM_VK_BACK_SPACE:
   203     case event.DOM_VK_DELETE:
   204       if (ed.config.mode == Editor.modes.css)
   205         private.completer.invalidateCache(ed.getCursor().line)
   206       private.doNotAutocomplete = true;
   207       private.popup.hidePopup();
   208       break;
   210     default:
   211       private.doNotAutocomplete = false;
   212   }
   213 }
   215 /**
   216  * Returns the private popup. This method is used by tests to test the feature.
   217  */
   218 function getPopup({ ed }) {
   219   return privates.get(ed).popup;
   220 }
   222 /**
   223  * Returns contextual information about the token covered by the caret if the
   224  * implementation of completer supports it.
   225  */
   226 function getInfoAt({ ed }, caret) {
   227   let completer = privates.get(ed).completer;
   228   if (completer && completer.getInfoAt)
   229     return completer.getInfoAt(ed.getText(), caret);
   231   return null;
   232 }
   234 // Export functions
   236 module.exports.setupAutoCompletion = setupAutoCompletion;
   237 module.exports.getAutocompletionPopup = getPopup;
   238 module.exports.getInfoAt = getInfoAt;

mercurial