Make edit file input an autocomplete

Uses the /files endpoint to query possible files.

Bug: Issue 4437
Change-Id: I439100b5f85de05cba8988daa3fd71502b6af07f
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
index 7ddf9a9..62ed476 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
@@ -16,13 +16,14 @@
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 
-<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
 <link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
 <link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
 <link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <link rel="import" href="../../../styles/shared-styles.html">
 
@@ -41,21 +42,21 @@
         margin-left: 1em;
         text-decoration: none;
       }
-      paper-input {
-        --paper-input-container: {
-          padding: 0;
-          min-width: 15em;
-        }
-        --paper-input-container-input: {
-          font-size: 1em;
-        }
-      }
       gr-confirm-dialog {
         width: 50em;
       }
       gr-confirm-dialog .main {
         width: 100%;
       }
+      gr-autocomplete {
+        --gr-autocomplete: {
+          border: 1px solid #d1d2d3;
+          border-radius: 2px;
+          font-size: 1em;
+          height: 2em;
+          padding: 0 .15em;
+        }
+      }
     </style>
     <template is="dom-repeat" items="[[_actions]]" as="action">
       <gr-button
@@ -74,13 +75,15 @@
         <div class="header">Edit a file</div>
         <div class="main">
           <!-- TODO(kaspern): Make this an autocomplete. -->
-          <paper-input
+          <gr-autocomplete
               class="input"
-              label="Enter an existing or new full file path."
-              value="{{_path}}"></paper-input>
+              placeholder="Enter an existing or new full file path."
+              query="[[_query]]"
+              text="{{_path}}"></gr-autocomplete>
         </div>
       </gr-confirm-dialog>
     </gr-overlay>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-edit-controls.js"></script>
 </dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
index 62e8c7a..625f145 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
@@ -37,8 +37,18 @@
         type: String,
         value: '',
       },
+      _query: {
+        type: Function,
+        value() {
+          return this._queryFiles.bind(this);
+        },
+      },
     },
 
+    behaviors: [
+      Gerrit.PatchSetBehavior,
+    ],
+
     _handleTap(e) {
       e.preventDefault();
       const action = Polymer.dom(e).localTarget.id;
@@ -73,7 +83,8 @@
     },
 
     _closeDialog(dialog) {
-      dialog.querySelectorAll('.input').forEach(input => { input.value = ''; });
+      dialog.querySelectorAll('gr-autocomplete')
+          .forEach(input => { input.text = ''; });
       dialog.classList.toggle('invisible', true);
       return this.$.overlay.close();
     },
@@ -87,5 +98,12 @@
       Gerrit.Nav.navigateToRelativeUrl(url);
       this._closeDialog(Polymer.dom(e).localTarget);
     },
+
+    _queryFiles(input) {
+      return this.$.restAPI.queryChangeFiles(this.change._number,
+          this.EDIT_NAME, input).then(res => res.map(file => {
+            return {name: file};
+          }));
+    },
   });
 })();
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index 6da4e32..d563393 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -37,12 +37,16 @@
   let sandbox;
   let showDialogSpy;
   let closeDialogSpy;
+  let queryStub;
 
   setup(() => {
     sandbox = sinon.sandbox.create();
     element = fixture('basic');
+    element.change = {_number: '42'};
     showDialogSpy = sandbox.spy(element, '_showDialog');
     closeDialogSpy = sandbox.spy(element, '_closeDialog');
+    queryStub = sandbox.stub(element.$.restAPI, 'queryChangeFiles')
+        .returns(Promise.resolve([]));
     flushAsynchronousOperations();
   });
 
@@ -67,7 +71,9 @@
       MockInteractions.tap(element.$$('#edit'));
       return showDialogSpy.lastCall.returnValue.then(() => {
         assert.isTrue(element.$.editDialog.disabled);
-        element._path = 'src/test.cpp';
+        assert.isFalse(queryStub.called);
+        element.$.editDialog.querySelector('.input').text = 'src/test.cpp';
+        assert.isTrue(queryStub.called);
         assert.isFalse(element.$.editDialog.disabled);
         MockInteractions.tap(element.$.editDialog.$$('gr-button[primary]'));
         for (const stub of navStubs) { assert.isTrue(stub.called); }
@@ -79,7 +85,7 @@
       MockInteractions.tap(element.$$('#edit'));
       return showDialogSpy.lastCall.returnValue.then(() => {
         assert.isTrue(element.$.editDialog.disabled);
-        element._path = 'src/test.cpp';
+        element.$.editDialog.querySelector('.input').text = 'src/test.cpp';
         assert.isFalse(element.$.editDialog.disabled);
         MockInteractions.tap(element.$.editDialog.$$('gr-button'));
         for (const stub of navStubs) { assert.isFalse(stub.called); }
@@ -92,7 +98,7 @@
   test('openEditDialog', () => {
     return element.openEditDialog('test/path.cpp').then(() => {
       assert.isFalse(element.$.editDialog.hasAttribute('hidden'));
-      assert.equal(element.$.editDialog.querySelector('.input').value,
+      assert.equal(element.$.editDialog.querySelector('.input').text,
           'test/path.cpp');
     });
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index c0449be..431ac0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -46,6 +46,7 @@
       },
       suggestions: {
         type: Array,
+        value: () => [],
         observer: '_resetCursorStops',
       },
       _suggestionEls: {
@@ -151,8 +152,12 @@
     },
 
     _resetCursorStops() {
-      Polymer.dom.flush();
-      this._suggestionEls = this.$.suggestions.querySelectorAll('li');
+      if (this.suggestions.length > 0) {
+        Polymer.dom.flush();
+        this._suggestionEls = this.$.suggestions.querySelectorAll('li');
+      } else {
+        this._suggestionEls = [];
+      }
     },
 
     _resetCursorIndex() {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index 7aa7abf..ab847c4 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -38,31 +38,29 @@
         color: red;
       }
     </style>
-    <div>
-      <input
-          id="input"
-          class$="[[_computeClass(borderless)]]"
-          is="iron-input"
-          disabled$="[[disabled]]"
-          bind-value="{{text}}"
-          placeholder="[[placeholder]]"
-          on-keydown="_handleKeydown"
-          on-focus="_onInputFocus"
-          on-blur="_onInputBlur"
-          autocomplete="off"/>
-      <gr-autocomplete-dropdown
-          vertical-align="top"
-          vertical-offset="20"
-          horizontal-align="auto"
-          id="suggestions"
-          on-item-selected="_handleItemSelect"
-          on-keydown="_handleKeydown"
-          suggestions="[[_suggestions]]"
-          role="listbox"
-          index="[[_index]]"
-          position-target="[[_inputElement]]">
-      </gr-autocomplete-dropdown>
-    </div>
+    <input
+        id="input"
+        class$="[[_computeClass(borderless)]]"
+        is="iron-input"
+        disabled$="[[disabled]]"
+        bind-value="{{text}}"
+        placeholder="[[placeholder]]"
+        on-keydown="_handleKeydown"
+        on-focus="_onInputFocus"
+        on-blur="_onInputBlur"
+        autocomplete="off"/>
+    <gr-autocomplete-dropdown
+        vertical-align="top"
+        vertical-offset="20"
+        horizontal-align="auto"
+        id="suggestions"
+        on-item-selected="_handleItemSelect"
+        on-keydown="_handleKeydown"
+        suggestions="[[_suggestions]]"
+        role="listbox"
+        index="[[_index]]"
+        position-target="[[_inputElement]]">
+    </gr-autocomplete-dropdown>
   </template>
   <script src="gr-autocomplete.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index c206b20..033d01b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -904,6 +904,17 @@
           patchRange.patchNum);
     },
 
+    /**
+     * @param {number|string} changeNum
+     * @param {number|string} patchNum
+     * @param {string} query
+     * @return {!Promise<!Object>}
+     */
+    queryChangeFiles(changeNum, patchNum, query) {
+      return this._getChangeURLAndFetch(changeNum,
+          `/files?q=${encodeURIComponent(query)}`, patchNum);
+    },
+
     getChangeFilesAsSpeciallySortedArray(changeNum, patchRange) {
       return this.getChangeFiles(changeNum, patchRange).then(
           this._normalizeChangeFilesResponse.bind(this));
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index eb4f418..aa7f9a0 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -701,6 +701,15 @@
       assert.equal(sendStub.lastCall.args[1], '/projects/x%2Fy');
     });
 
+    test('queryChangeFiles', () => {
+      const fetchStub = sandbox.stub(element, '_getChangeURLAndFetch')
+          .returns(Promise.resolve());
+      return element.queryChangeFiles('42', 'edit', 'test/path.js').then(() => {
+        assert.deepEqual(fetchStub.lastCall.args,
+            ['42', '/files?q=test%2Fpath.js', 'edit']);
+      });
+    });
+
     test('getProjects', () => {
       sandbox.stub(element, '_fetchSharedCacheURL');
       element.getProjects('test', 25);