Merge "Add notify() calls to coverage layer for re-rendering"
diff --git a/modules/jgit b/modules/jgit
index 2021ce3..e81c513 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 2021ce3423a7db6949b9e0a71a8c15e5826ccc4c
+Subproject commit e81c5135fefb33103b12c8132a4fd5101f0c7b26
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
index 87761a6..8824009 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.ts
@@ -3,7 +3,6 @@
  * Copyright 2017 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '../../shared/gr-account-label/gr-account-label';
 import '../../shared/gr-autocomplete/gr-autocomplete';
 import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index 2a648b1..bb1e926 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -3,7 +3,6 @@
  * Copyright 2017 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
 import '../../plugins/gr-endpoint-param/gr-endpoint-param';
 import '../../shared/gr-button/gr-button';
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index f1e70b0..bbca52d 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -35,6 +35,9 @@
 import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
 import {userModelToken} from '../../../models/user/user-model';
 import {modalStyles} from '../../../styles/gr-modal-styles';
+import {GrSyntaxLayerWorker} from '../../../embed/diff/gr-syntax-layer/gr-syntax-layer-worker';
+import {highlightServiceToken} from '../../../services/highlight/highlight-service';
+import {anyLineTooLong} from '../../../embed/diff/gr-diff/gr-diff-utils';
 
 interface FilePreview {
   filepath: string;
@@ -94,15 +97,22 @@
 
   private readonly getNavigation = resolve(this, navigationToken);
 
+  private readonly syntaxLayer = new GrSyntaxLayerWorker(
+    resolve(this, highlightServiceToken),
+    () => getAppContext().reportingService
+  );
+
   constructor() {
     super();
     subscribe(
       this,
       () => this.getUserModel().preferences$,
       preferences => {
+        const layers: DiffLayer[] = [this.syntaxLayer];
         if (!preferences?.disable_token_highlighting) {
-          this.layers = [new TokenHighlightLayer(this)];
+          layers.push(new TokenHighlightLayer(this));
         }
+        this.layers = layers;
       }
     );
     subscribe(
@@ -111,6 +121,7 @@
       diffPreferences => {
         if (!diffPreferences) return;
         this.diffPrefs = diffPreferences;
+        this.syntaxLayer.setEnabled(!!this.diffPrefs.syntax_highlighting);
       }
     );
   }
@@ -173,19 +184,25 @@
         <div class="file-name">
           <span>${item.filepath}</span>
         </div>
-        <div class="diffContainer">
-          <gr-diff
-            .prefs=${this.overridePartialDiffPrefs()}
-            .path=${item.filepath}
-            .diff=${item.preview}
-            .layers=${this.layers}
-          ></gr-diff>
-        </div>
+        <div class="diffContainer">${this.renderDiff(item)}</div>
       `
     );
     return html`<div slot="main">${items}</div>`;
   }
 
+  private renderDiff(preview: FilePreview) {
+    const diff = preview.preview;
+    if (!anyLineTooLong(diff)) {
+      this.syntaxLayer.process(diff);
+    }
+    return html`<gr-diff
+      .prefs=${this.overridePartialDiffPrefs()}
+      .path=${preview.filepath}
+      .diff=${diff}
+      .layers=${this.layers}
+    ></gr-diff>`;
+  }
+
   private renderFooter() {
     const id = this.selectedFixIdx;
     const fixCount = this.fixSuggestions?.length ?? 0;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 7d3fe6f..36c9397 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -698,6 +698,7 @@
     );
     this.addEventListener('open-fix-preview', e => this.onOpenFixPreview(e));
     this.cursor = new GrDiffCursor();
+    if (this.diffHost) this.reInitCursor();
   }
 
   override disconnectedCallback() {
diff --git a/polygerrit-ui/app/elements/gr-css-mixins.ts b/polygerrit-ui/app/elements/gr-css-mixins.ts
index 733b386..523a056 100644
--- a/polygerrit-ui/app/elements/gr-css-mixins.ts
+++ b/polygerrit-ui/app/elements/gr-css-mixins.ts
@@ -77,6 +77,10 @@
           --paper-listbox: {
             padding: 0;
           };
+          --iron-autogrow-textarea: {
+            box-sizing: border-box;
+            padding: var(--spacing-s);
+          };
         }
       </style>
     `;
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index f63f34e..1549da5 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -3,6 +3,7 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
+import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '@polymer/iron-input/iron-input';
 import '../../shared/gr-avatar/gr-avatar';
 import '../../shared/gr-date-formatter/gr-date-formatter';
@@ -87,6 +88,13 @@
         border-radius: var(--border-radius);
         box-shadow: var(--elevation-level-5);
       }
+      iron-autogrow-textarea {
+        background-color: var(--view-background-color);
+        color: var(--primary-text-color);
+      }
+      .lengthCounter {
+        font-weight: var(--font-weight-normal);
+      }
     `,
   ];
 
@@ -200,25 +208,26 @@
         </span>
       </section>
       <section>
-        <label class="title" for="statusInput">About me (e.g. employer)</label>
+        <span class="title">
+          <label for="statusInput">About me (e.g. employer)</label>
+          <div class="lengthCounter">
+            ${this.account.status?.length ?? 0}/140
+          </div>
+        </span>
         <span class="value">
-          <iron-input
-            id="statusIronInput"
-            @keydown=${this.handleKeydown}
-            .bindValue=${this.account?.status}
+          <iron-autogrow-textarea
+            id="statusInput"
+            .name=${'statusInput'}
+            ?disabled=${this.saving}
+            maxlength="140"
+            .value=${this.account?.status}
             @bind-value-changed=${(e: BindValueChangeEvent) => {
               const oldAccount = this.account;
               if (!oldAccount || oldAccount.status === e.detail.value) return;
               this.account = {...oldAccount, status: e.detail.value};
               this.hasStatusChange = true;
             }}
-          >
-            <input
-              id="statusInput"
-              ?disabled=${this.saving}
-              @keydown=${this.handleKeydown}
-            />
-          </iron-input>
+          ></iron-autogrow-textarea>
         </span>
       </section>
       <section>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index f7e5dee..e968b12 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -5,7 +5,12 @@
  */
 import '../../../test/common-test-setup';
 import './gr-account-info';
-import {query, queryAll, stubRestApi} from '../../../test/test-utils';
+import {
+  query,
+  queryAll,
+  queryAndAssert,
+  stubRestApi,
+} from '../../../test/test-utils';
 import {GrAccountInfo} from './gr-account-info';
 import {AccountDetailInfo, ServerInfo} from '../../../types/common';
 import {
@@ -20,6 +25,7 @@
 import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
 import {EditableAccountField} from '../../../api/rest-api';
 import {fixture, html, assert} from '@open-wc/testing';
+import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea';
 
 suite('gr-account-info tests', () => {
   let element!: GrAccountInfo;
@@ -100,13 +106,16 @@
             </span>
           </section>
           <section>
-            <label class="title" for="statusInput">
-              About me (e.g. employer)
-            </label>
+            <span class="title">
+              <label for="statusInput">About me (e.g. employer)</label>
+              <div class="lengthCounter">0/140</div>
+            </span>
             <span class="value">
-              <iron-input id="statusIronInput">
-                <input id="statusInput" />
-              </iron-input>
+              <iron-autogrow-textarea
+                aria-disabled="false"
+                id="statusInput"
+                maxlength="140"
+              />
             </span>
           </section>
           <section>
@@ -276,8 +285,11 @@
     test('status', async () => {
       assert.isFalse(element.hasUnsavedChanges);
 
-      const statusInputEl = queryIronInput('#statusIronInput');
-      statusInputEl.bindValue = 'new status';
+      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+        element,
+        '#statusInput'
+      );
+      statusTextarea.value = 'new status';
       await element.updateComplete;
       assert.isFalse(element.hasNameChange);
       assert.isTrue(element.hasStatusChange);
@@ -320,8 +332,11 @@
       await element.updateComplete;
       assert.isTrue(element.hasNameChange);
 
-      const statusInputEl = queryIronInput('#statusIronInput');
-      statusInputEl.bindValue = 'new status';
+      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+        element,
+        '#statusInput'
+      );
+      statusTextarea.value = 'new status';
       await element.updateComplete;
       assert.isTrue(element.hasStatusChange);
 
@@ -366,8 +381,11 @@
       assert.equal(displaySpan.textContent, account.name);
       assert.isUndefined(inputSpan);
 
-      const inputEl = queryIronInput('#statusIronInput');
-      inputEl.bindValue = 'new status';
+      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+        element,
+        '#statusInput'
+      );
+      statusTextarea.value = 'new status';
       await element.updateComplete;
       assert.isTrue(element.hasStatusChange);
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index d9004ae..5306cea 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -3,7 +3,6 @@
  * Copyright 2015 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '../../../styles/shared-styles';
 import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
 import '../../plugins/gr-endpoint-param/gr-endpoint-param';
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
index dc08648..abc7f3c 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
@@ -157,6 +157,9 @@
         .reason {
           padding-top: var(--spacing-s);
         }
+        .status .value {
+          white-space: pre-wrap;
+        }
       `,
     ];
   }
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
index 92396b0..6cd3cb0 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -169,6 +169,9 @@
   }
 
   render(keyLocations: KeyLocations): Promise<void> {
+    assertIsDefined(this.diff, 'diff');
+    assertIsDefined(this.diffElement, 'diff table');
+
     // Setting up annotation layers must happen after plugins are
     // installed, and |render| satisfies the requirement, however,
     // |attached| doesn't because in the diff view page, the element is
@@ -178,22 +181,13 @@
     this.showTabs = this.prefs.show_tabs;
     this.showTrailingWhitespace = this.prefs.show_whitespace_errors;
 
-    // Stop the processor if it's running.
-    this.cancel();
-
-    this.builder?.clear();
-    assertIsDefined(this.diff, 'diff');
-    assertIsDefined(this.diffElement, 'diff table');
+    this.cleanup();
     this.builder = this.getDiffBuilder();
+    this.init();
 
     this.processor.context = this.prefs.context;
     this.processor.keyLocations = keyLocations;
 
-    this.diffElement.addEventListener(
-      'diff-context-expanded',
-      this.onDiffContextExpanded
-    );
-
     this.clearDiffContent();
     this.builder.addColumns(
       this.diffElement,
@@ -370,9 +364,32 @@
     });
   }
 
-  cancel() {
+  /**
+   * This is meant to be called when the gr-diff component re-connects, or when
+   * the diff is (re-)rendered.
+   *
+   * Make sure that this method is symmetric with cleanup(), which is called
+   * when gr-diff disconnects.
+   */
+  init() {
+    this.cleanup();
+    this.diffElement?.addEventListener(
+      'diff-context-expanded',
+      this.onDiffContextExpanded
+    );
+    this.builder?.init();
+  }
+
+  /**
+   * This is meant to be called when the gr-diff component disconnects, or when
+   * the diff is (re-)rendered.
+   *
+   * Make sure that this method is symmetric with init(), which is called when
+   * gr-diff re-connects.
+   */
+  cleanup() {
     this.processor.cancel();
-    this.builder?.clear();
+    this.builder?.cleanup();
     this.cancelableRenderPromise?.cancel();
     this.cancelableRenderPromise = null;
     this.diffElement?.removeEventListener(
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
index 2cfb895..210b8b2 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
@@ -648,7 +648,7 @@
 
     test('cancel cancels the processor', () => {
       const processorCancelStub = sinon.stub(element.processor, 'cancel');
-      element.cancel();
+      element.cleanup();
       assert.isTrue(processorCancelStub.called);
     });
   });
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
index 0006f26..f3c88a9 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
@@ -38,7 +38,8 @@
  * of this class is. There are no plans for adding separate implementations.
  */
 export interface DiffBuilder {
-  clear(): void;
+  init(): void;
+  cleanup(): void;
   addGroups(groups: readonly GrDiffGroup[]): void;
   clearGroups(): void;
   replaceGroup(
@@ -127,6 +128,18 @@
       end: LineNumber,
       side: Side
     ) => this.renderContentByRange(start, end, side);
+    this.init();
+  }
+
+  /**
+   * This is meant to be called when the gr-diff component re-connects, or when
+   * the diff is (re-)rendered.
+   *
+   * Make sure that this method is symmetric with cleanup(), which is called
+   * when gr-diff disconnects.
+   */
+  init() {
+    this.cleanup();
     for (const layer of this.layers) {
       if (layer.addListener) {
         layer.addListener(this.layerUpdateListener);
@@ -134,7 +147,14 @@
     }
   }
 
-  clear() {
+  /**
+   * This is meant to be called when the gr-diff component disconnects, or when
+   * the diff is (re-)rendered.
+   *
+   * Make sure that this method is symmetric with init(), which is called when
+   * gr-diff re-connects.
+   */
+  cleanup() {
     for (const layer of this.layers) {
       if (layer.removeListener) {
         layer.removeListener(this.layerUpdateListener);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 55fb1d1..6029e65 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -996,6 +996,12 @@
     if (this.loggedIn) {
       this.addSelectionListeners();
     }
+    if (this.diff && this.diffTable) {
+      this.diffSelection.init(this.diff, this.diffTable);
+    }
+    if (this.diffTable && this.diffBuilder) {
+      this.highlights.init(this.diffTable, this.diffBuilder);
+    }
   }
 
   override disconnectedCallback() {
@@ -1003,7 +1009,7 @@
     this.renderDiffTableTask?.cancel();
     this.diffSelection.cleanup();
     this.highlights.cleanup();
-    this.diffBuilder.cancel();
+    this.diffBuilder.cleanup();
     super.disconnectedCallback();
   }
 
@@ -1264,7 +1270,7 @@
 
   /** Cancel any remaining diff builder rendering work. */
   cancel() {
-    this.diffBuilder.cancel();
+    this.diffBuilder.cleanup();
     this.renderDiffTableTask?.cancel();
   }
 
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index f657973..00e46c7 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -1857,9 +1857,9 @@
   });
 
   test('cancel', () => {
-    const cancelStub = sinon.stub(element.diffBuilder, 'cancel');
+    const cleanupStub = sinon.stub(element.diffBuilder, 'cleanup');
     element.cancel();
-    assert.isTrue(cancelStub.calledOnce);
+    assert.isTrue(cleanupStub.calledOnce);
   });
 
   test('line limit with line_wrapping', async () => {
diff --git a/polygerrit-ui/app/styles/shared-styles.ts b/polygerrit-ui/app/styles/shared-styles.ts
index 839f612..5a7ca48 100644
--- a/polygerrit-ui/app/styles/shared-styles.ts
+++ b/polygerrit-ui/app/styles/shared-styles.ts
@@ -124,13 +124,8 @@
     /* iron-autogrow-textarea has a "-webkit-appearance: textarea" :host
         css rule, which prevents overriding the border color. Clear that. */
     -webkit-appearance: none;
-    --iron-autogrow-textarea: {
-      box-sizing: border-box;
-      padding: var(--spacing-s);
-    };
     --iron-autogrow-textarea_-_box-sizing: border-box;
     --iron-autogrow-textarea_-_padding: var(--spacing-s);
-    --iron-autogrow-textarea_-_white-space: pre-wrap;
   }
   a {
     color: var(--link-color);