<commit_before>// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as LitHtml from '../third_party/lit-html/lit-html.js';

import {toHexString} from './LinearMemoryInspectorUtils.js';

const {render, html} = LitHtml;

export interface LinearMemoryViewerData {
  memory: Uint8Array;
  address: number;
  memoryOffset: number;
  focus: boolean;
}

export class ByteSelectedEvent extends Event {
  data: number;

  constructor(address: number) {
    super('byte-selected');
    this.data = address;
  }
}

export class ResizeEvent extends Event {
  data: number;

  constructor(numBytesPerPage: number) {
    super('resize');
    this.data = numBytesPerPage;
  }
}
export class LinearMemoryViewer extends HTMLElement {
  private static BYTE_GROUP_MARGIN = 8;
  private static BYTE_GROUP_SIZE = 4;
  private readonly shadow = this.attachShadow({mode: 'open'});

  private readonly resizeObserver = new ResizeObserver(() => this.resize());
  private isObservingResize = false;

  private memory = new Uint8Array();
  private address = 0;
  private memoryOffset = 0;

  private numRows = 1;
  private numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;

  private focusOnByte = true;

  private lastKeyUpdateSent: number|undefined = undefined;

  set data(data: LinearMemoryViewerData) {
    if (data.address < data.memoryOffset || data.address > data.memoryOffset + data.memory.length || data.address < 0) {
      throw new Error('Address is out of bounds.');
    }

    if (data.memoryOffset < 0) {
      throw new Error('Memory offset has to be greater or equal to zero.');
    }

    this.memory = data.memory;
    this.address = data.address;
    this.memoryOffset = data.memoryOffset;
    this.focusOnByte = data.focus;
    this.update();
  }

  disconnectedCallback(): void {
    this.isObservingResize = false;
    this.resizeObserver.disconnect();
  }

  private update(): void {
    this.updateDimensions();
    this.render();
    this.focusOnView();
    this.engageResizeObserver();
  }

  private focusOnView(): void {
    if (this.focusOnByte) {
      const view = this.shadow.querySelector<HTMLDivElement>('.view');
      if (view) {
        view.focus();
      }
    }
  }

  private resize(): void {
    // A memory request currently takes too much time, so for the time being
    // update with whatever data we have, and request for more memory to fill
    // the screen if applicable after.
    this.update();
    this.dispatchEvent(new ResizeEvent(this.numBytesInRow * this.numRows));
  }

  /** Recomputes the number of rows and (byte) columns that fit into the current view. */
  private updateDimensions(): void {
    if (this.clientWidth === 0 || this.clientHeight === 0 || !this.shadowRoot) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    // We initially just plot one row with one byte group (here: byte group size of 4).
    // Depending on that initially plotted row we can determine how many rows and
    // bytes per row we can fit:
    // > 0000000 | b0 b1 b2 b4 | a0 a1 a2 a3       <
    //             ^-^           ^-^
    //             byteCellWidth textCellWidth
    //             ^-------------------------------^
    //                 widthToFill

    const firstByteCell = this.shadowRoot.querySelector('.byte-cell');
    const textCell = this.shadowRoot.querySelector('.text-cell');
    const divider = this.shadowRoot.querySelector('.divider');
    const rowElement = this.shadowRoot.querySelector('.row');

    if (!firstByteCell || !textCell || !divider || !rowElement) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    // Calculate the width required for each (unsplittable) group of bytes.
    const byteCellWidth = firstByteCell.getBoundingClientRect().width;
    const textCellWidth = textCell.getBoundingClientRect().width;
    const groupWidth =
        LinearMemoryViewer.BYTE_GROUP_SIZE * (byteCellWidth + textCellWidth) + LinearMemoryViewer.BYTE_GROUP_MARGIN;

    // Calculate the width to fill.
    const dividerWidth = divider.getBoundingClientRect().width;
    const widthToFill = this.clientWidth -
        (firstByteCell.getBoundingClientRect().left - this.getBoundingClientRect().left) - dividerWidth;
    if (widthToFill < groupWidth) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    this.numBytesInRow = Math.floor(widthToFill / groupWidth) * LinearMemoryViewer.BYTE_GROUP_SIZE;
    this.numRows = Math.floor(this.clientHeight / rowElement.clientHeight);
  }

  private engageResizeObserver(): void {
    if (!this.resizeObserver || this.isObservingResize) {
      return;
    }

    this.resizeObserver.observe(this);
    this.isObservingResize = true;
  }

  private render(): void {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    render(html`
      <style>
        :host {
          flex: auto;
          display: flex;
          min-height: 20px;
        }

        .view {
          overflow: hidden;
          text-overflow: ellipsis;
          box-sizing: border-box;
          background: var(--color-background);
          outline: none;
        }

        .row {
          display: flex;
          height: 20px;
          align-items: center;
        }

        .cell {
          text-align: center;
          border: 1px solid transparent;
          border-radius: 2px;
        }

        .cell.selected {
          border-color: var(--color-syntax-3);
          color: var(--color-syntax-3);
          background-color: var(--item-selection-bg-color);
        }

        .byte-cell {
          min-width: 21px;
          color: var(--color-text-primary)
        }

        .byte-group-margin {
          margin-left: ${LinearMemoryViewer.BYTE_GROUP_MARGIN}px;
        }<commit_msg>Use custom CSS property rather than string interpolation<commit_after>// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as LitHtml from '../third_party/lit-html/lit-html.js';

import {toHexString} from './LinearMemoryInspectorUtils.js';

const {render, html} = LitHtml;

export interface LinearMemoryViewerData {
  memory: Uint8Array;
  address: number;
  memoryOffset: number;
  focus: boolean;
}

export class ByteSelectedEvent extends Event {
  data: number;

  constructor(address: number) {
    super('byte-selected');
    this.data = address;
  }
}

export class ResizeEvent extends Event {
  data: number;

  constructor(numBytesPerPage: number) {
    super('resize');
    this.data = numBytesPerPage;
  }
}
export class LinearMemoryViewer extends HTMLElement {
  private static BYTE_GROUP_MARGIN = 8;
  private static BYTE_GROUP_SIZE = 4;
  private readonly shadow = this.attachShadow({mode: 'open'});

  private readonly resizeObserver = new ResizeObserver(() => this.resize());
  private isObservingResize = false;

  private memory = new Uint8Array();
  private address = 0;
  private memoryOffset = 0;

  private numRows = 1;
  private numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;

  private focusOnByte = true;

  private lastKeyUpdateSent: number|undefined = undefined;

  set data(data: LinearMemoryViewerData) {
    if (data.address < data.memoryOffset || data.address > data.memoryOffset + data.memory.length || data.address < 0) {
      throw new Error('Address is out of bounds.');
    }

    if (data.memoryOffset < 0) {
      throw new Error('Memory offset has to be greater or equal to zero.');
    }

    this.memory = data.memory;
    this.address = data.address;
    this.memoryOffset = data.memoryOffset;
    this.focusOnByte = data.focus;
    this.update();
  }

  connectedCallback(): void {
    (this.shadow.host as HTMLElement)
        .style.setProperty('--byte-group-margin', `${LinearMemoryViewer.BYTE_GROUP_MARGIN}px`);
  }

  disconnectedCallback(): void {
    this.isObservingResize = false;
    this.resizeObserver.disconnect();
  }

  private update(): void {
    this.updateDimensions();
    this.render();
    this.focusOnView();
    this.engageResizeObserver();
  }

  private focusOnView(): void {
    if (this.focusOnByte) {
      const view = this.shadow.querySelector<HTMLDivElement>('.view');
      if (view) {
        view.focus();
      }
    }
  }

  private resize(): void {
    // A memory request currently takes too much time, so for the time being
    // update with whatever data we have, and request for more memory to fill
    // the screen if applicable after.
    this.update();
    this.dispatchEvent(new ResizeEvent(this.numBytesInRow * this.numRows));
  }

  /** Recomputes the number of rows and (byte) columns that fit into the current view. */
  private updateDimensions(): void {
    if (this.clientWidth === 0 || this.clientHeight === 0 || !this.shadowRoot) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    // We initially just plot one row with one byte group (here: byte group size of 4).
    // Depending on that initially plotted row we can determine how many rows and
    // bytes per row we can fit:
    // > 0000000 | b0 b1 b2 b4 | a0 a1 a2 a3       <
    //             ^-^           ^-^
    //             byteCellWidth textCellWidth
    //             ^-------------------------------^
    //                 widthToFill

    const firstByteCell = this.shadowRoot.querySelector('.byte-cell');
    const textCell = this.shadowRoot.querySelector('.text-cell');
    const divider = this.shadowRoot.querySelector('.divider');
    const rowElement = this.shadowRoot.querySelector('.row');

    if (!firstByteCell || !textCell || !divider || !rowElement) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    // Calculate the width required for each (unsplittable) group of bytes.
    const byteCellWidth = firstByteCell.getBoundingClientRect().width;
    const textCellWidth = textCell.getBoundingClientRect().width;
    const groupWidth =
        LinearMemoryViewer.BYTE_GROUP_SIZE * (byteCellWidth + textCellWidth) + LinearMemoryViewer.BYTE_GROUP_MARGIN;

    // Calculate the width to fill.
    const dividerWidth = divider.getBoundingClientRect().width;
    const widthToFill = this.clientWidth -
        (firstByteCell.getBoundingClientRect().left - this.getBoundingClientRect().left) - dividerWidth;
    if (widthToFill < groupWidth) {
      this.numBytesInRow = LinearMemoryViewer.BYTE_GROUP_SIZE;
      this.numRows = 1;
      return;
    }

    this.numBytesInRow = Math.floor(widthToFill / groupWidth) * LinearMemoryViewer.BYTE_GROUP_SIZE;
    this.numRows = Math.floor(this.clientHeight / rowElement.clientHeight);
  }

  private engageResizeObserver(): void {
    if (!this.resizeObserver || this.isObservingResize) {
      return;
    }

    this.resizeObserver.observe(this);
    this.isObservingResize = true;
  }

  private render(): void {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    render(html`
      <style>
        :host {
          flex: auto;
          display: flex;
          min-height: 20px;
        }

        .view {
          overflow: hidden;
          text-overflow: ellipsis;
          box-sizing: border-box;
          background: var(--color-background);
          outline: none;
        }

        .row {
          display: flex;
          height: 20px;
          align-items: center;
        }

        .cell {
          text-align: center;
          border: 1px solid transparent;
          border-radius: 2px;
        }

        .cell.selected {
          border-color: var(--color-syntax-3);
          color: var(--color-syntax-3);
          background-color: var(--item-selection-bg-color);
        }

        .byte-cell {
          min-width: 21px;
          color: var(--color-text-primary)
        }

        .byte-group-margin {
          margin-left: var(--byte-group-margin);
        }
{'fraction_non_alphanumeric': 0.11369403290496849, 'fraction_numerical': 0.008349021854792502, 'mean_word_length': 2.5787932044522552, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 13, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1787499', 'n_tokens_mistral': 3681, 'n_tokens_neox': 3530, 'n_words': 912}