<commit_before>/**
 * Copyright (c) 2017 The xterm.js authors. All rights reserved.
 * @license MIT
 */

import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm';
import { EventEmitter } from 'common/EventEmitter';

export interface ISearchOptions {
  regex?: boolean;
  wholeWord?: boolean;
  caseSensitive?: boolean;
  incremental?: boolean;
  decorations?: ISearchDecorationOptions;
  noScroll?: boolean;
}

interface ISearchDecorationOptions {
  matchBackground?: string;
  matchBorder?: string;
  matchOverviewRuler: string;
  activeMatchBackground?: string;
  activeMatchBorder?: string;
  activeMatchColorOverviewRuler: string;
}

export interface ISearchPosition {
  startCol: number;
  startRow: number;
}

export interface ISearchResult {
  term: string;
  col: number;
  row: number;
  size: number;
}

type LineCacheEntry = [
  /**
   * The string representation of a line (as opposed to the buffer cell representation).
   */
  lineAsString: string,
  /**
   * The offsets where each line starts when the entry describes a wrapped line.
   */
  lineOffsets: number[]
];

const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs

export class SearchAddon implements ITerminalAddon {
  private _terminal: Terminal | undefined;
  private _cachedSearchTerm: string | undefined;
  private _selectedDecoration: IDecoration | undefined;
  private _resultDecorations: Map<number, IDecoration[]> | undefined;
  private _searchResults:  Map<string, ISearchResult> | undefined;
  private _onDataDisposable: IDisposable | undefined;
  private _onResizeDisposable: IDisposable | undefined;
  private _lastSearchOptions: ISearchOptions | undefined;
  private _highlightTimeout: number | undefined;
  /**
   * translateBufferLineToStringWithWrap is a fairly expensive call.
   * We memoize the calls into an array that has a time based ttl.
   * _linesCache is also invalidated when the terminal cursor moves.
   */
  private _linesCache: LineCacheEntry[] | undefined;
  private _linesCacheTimeoutId = 0;
  private _cursorMoveListener: IDisposable | undefined;
  private _resizeListener: IDisposable | undefined;

  private _resultIndex: number | undefined;

  private readonly _onDidChangeResults = new EventEmitter<{resultIndex: number, resultCount: number} | undefined>();
  public readonly onDidChangeResults = this._onDidChangeResults.event;

  public activate(terminal: Terminal): void {
    this._terminal = terminal;
    this._onDataDisposable = this._terminal.onData(() => this._updateMatches());
    this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
  }

  private _updateMatches(): void {
    if (this._highlightTimeout) {
      window.clearTimeout(this._highlightTimeout);
    }
    if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) {
      this._highlightTimeout = setTimeout(() => {
        this.findPrevious(this._cachedSearchTerm!,  { ...this._lastSearchOptions, incremental: true, noScroll: true });
        this._onDidChangeResults.fire({ resultIndex: this._searchResults ? this._searchResults.size - 1 : -1, resultCount: this._searchResults ? this._searchResults.size : -1 });
      }, 200);
    }
  }

  public dispose(): void {
    this.clearDecorations();
    this._onDataDisposable?.dispose();
    this._onResizeDisposable?.dispose();
  }

  public clearDecorations(internal?: boolean): void {
    this._selectedDecoration?.dispose();
    this._searchResults?.clear();
    this._resultDecorations?.forEach(decorations => {
      for (const d of decorations) {
        d.dispose();
      }
    });
    this._resultDecorations?.clear();
    this._searchResults = undefined;
    this._resultDecorations = undefined;
    if (!internal) {
      // we want to keep _cachedSearchTerm defined if this is
      // an internal call
      // so that when the buffer changes,
      // we can use that to search for new matches
      this._cachedSearchTerm = undefined;
    }
  }

  /**
   * Find the next instance of the term, then scroll to and select it. If it
   * doesn't exist, do nothing.
   * @param term The search term.
   * @param searchOptions Search options.
   * @return Whether a result was found.
   */
  public findNext(term: string, searchOptions?: ISearchOptions): boolean {
    if (!this._terminal) {
      throw new Error('Cannot use addon until it has been loaded');
    }
    this._lastSearchOptions = searchOptions;
    if (searchOptions?.decorations) {
      if (this._resultIndex || this._cachedSearchTerm && term !== this._cachedSearchTerm) {
        this._highlightAllMatches(term, searchOptions);
      }
    }
    return this._fireResults(term, this._findNextAndSelect(term, searchOptions), searchOptions);
  }

  private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void {<commit_msg>refine name for clearDecorations param (#3757)<commit_after>/**
 * Copyright (c) 2017 The xterm.js authors. All rights reserved.
 * @license MIT
 */

import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm';
import { EventEmitter } from 'common/EventEmitter';

export interface ISearchOptions {
  regex?: boolean;
  wholeWord?: boolean;
  caseSensitive?: boolean;
  incremental?: boolean;
  decorations?: ISearchDecorationOptions;
  noScroll?: boolean;
}

interface ISearchDecorationOptions {
  matchBackground?: string;
  matchBorder?: string;
  matchOverviewRuler: string;
  activeMatchBackground?: string;
  activeMatchBorder?: string;
  activeMatchColorOverviewRuler: string;
}

export interface ISearchPosition {
  startCol: number;
  startRow: number;
}

export interface ISearchResult {
  term: string;
  col: number;
  row: number;
  size: number;
}

type LineCacheEntry = [
  /**
   * The string representation of a line (as opposed to the buffer cell representation).
   */
  lineAsString: string,
  /**
   * The offsets where each line starts when the entry describes a wrapped line.
   */
  lineOffsets: number[]
];

const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs

export class SearchAddon implements ITerminalAddon {
  private _terminal: Terminal | undefined;
  private _cachedSearchTerm: string | undefined;
  private _selectedDecoration: IDecoration | undefined;
  private _resultDecorations: Map<number, IDecoration[]> | undefined;
  private _searchResults:  Map<string, ISearchResult> | undefined;
  private _onDataDisposable: IDisposable | undefined;
  private _onResizeDisposable: IDisposable | undefined;
  private _lastSearchOptions: ISearchOptions | undefined;
  private _highlightTimeout: number | undefined;
  /**
   * translateBufferLineToStringWithWrap is a fairly expensive call.
   * We memoize the calls into an array that has a time based ttl.
   * _linesCache is also invalidated when the terminal cursor moves.
   */
  private _linesCache: LineCacheEntry[] | undefined;
  private _linesCacheTimeoutId = 0;
  private _cursorMoveListener: IDisposable | undefined;
  private _resizeListener: IDisposable | undefined;

  private _resultIndex: number | undefined;

  private readonly _onDidChangeResults = new EventEmitter<{resultIndex: number, resultCount: number} | undefined>();
  public readonly onDidChangeResults = this._onDidChangeResults.event;

  public activate(terminal: Terminal): void {
    this._terminal = terminal;
    this._onDataDisposable = this._terminal.onData(() => this._updateMatches());
    this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
  }

  private _updateMatches(): void {
    if (this._highlightTimeout) {
      window.clearTimeout(this._highlightTimeout);
    }
    if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) {
      this._highlightTimeout = setTimeout(() => {
        this.findPrevious(this._cachedSearchTerm!,  { ...this._lastSearchOptions, incremental: true, noScroll: true });
        this._onDidChangeResults.fire({ resultIndex: this._searchResults ? this._searchResults.size - 1 : -1, resultCount: this._searchResults ? this._searchResults.size : -1 });
      }, 200);
    }
  }

  public dispose(): void {
    this.clearDecorations();
    this._onDataDisposable?.dispose();
    this._onResizeDisposable?.dispose();
  }

  public clearDecorations(retainCachedSearchTerm?: boolean): void {
    this._selectedDecoration?.dispose();
    this._searchResults?.clear();
    this._resultDecorations?.forEach(decorations => {
      for (const d of decorations) {
        d.dispose();
      }
    });
    this._resultDecorations?.clear();
    this._searchResults = undefined;
    this._resultDecorations = undefined;
    if (!retainCachedSearchTerm) {
      this._cachedSearchTerm = undefined;
    }
  }

  /**
   * Find the next instance of the term, then scroll to and select it. If it
   * doesn't exist, do nothing.
   * @param term The search term.
   * @param searchOptions Search options.
   * @return Whether a result was found.
   */
  public findNext(term: string, searchOptions?: ISearchOptions): boolean {
    if (!this._terminal) {
      throw new Error('Cannot use addon until it has been loaded');
    }
    this._lastSearchOptions = searchOptions;
    if (searchOptions?.decorations) {
      if (this._resultIndex || this._cachedSearchTerm && term !== this._cachedSearchTerm) {
        this._highlightAllMatches(term, searchOptions);
      }
    }
    return this._fireResults(term, this._findNextAndSelect(term, searchOptions), searchOptions);
  }

  private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void {
{'fraction_non_alphanumeric': 0.10649296939619521, 'fraction_numerical': 0.004342431761786601, 'mean_word_length': 4.109878499735869, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, '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': '2756454', 'n_tokens_mistral': 2827, 'n_tokens_neox': 2714, 'n_words': 744}