<commit_before>import { Arr, Fun, Option, Unicode } from '@ephox/katamari';

export interface CharPos {
  ch: () => string;
  offset: () => number;
}

const charpos = (ch: string, offset: number): CharPos => ({
  ch: Fun.constant(ch),
  offset:  Fun.constant(offset)
});

const locate = function (text: string, offset: number) {
  return charpos(text.charAt(offset), offset);
};

const previous = function (text: string, offsetOption: Option<number>) {
  const max = offsetOption.getOr(text.length);
  for (let i = max - 1; i >= 0; i--) {
    if (text.charAt(i) !== Unicode.zeroWidth) {
      return Option.some(locate(text, i));
    }
  }
  return Option.none<CharPos>();
};

const next = function (text: string, offsetOption: Option<number>) {
  const min = offsetOption.getOr(0);
  for (let i = min + 1; i < text.length; i++) {
    if (text.charAt(i) !== Unicode.zeroWidth) {
      return Option.some(locate(text, i));
    }
  }
  return Option.none<CharPos>();
};

const rfind = function (str: string, regex: RegExp) {
  regex.lastIndex = -1;
  const reversed = Arr.reverse(str).join('');
  const match = reversed.match(regex);
  return match !== undefined && match !== null && match.index !== undefined && match.index >= 0 ? Option.some((reversed.length - 1) - match.index) : Option.none<number>();
};

const lfind = function (str: string, regex: RegExp) {
  regex.lastIndex = -1;
  const match = str.match(regex);
  return match !== undefined && match !== null && match.index !== undefined && match.index >= 0 ? Option.some(match.index) : Option.none<number>();
};

export const TextSearch = {
  previous,
  next,
  rfind,
  lfind
};<commit_msg>Unthunk<commit_after>import { Arr, Option, Unicode } from '@ephox/katamari';

export interface CharPos {
  readonly ch: string;
  readonly offset: number;
}

const charpos = (ch: string, offset: number): CharPos => ({
  ch,
  offset
});

const locate = (text: string, offset: number): CharPos =>
  charpos(text.charAt(offset), offset);

const previous = (text: string, offsetOption: Option<number>): Option<CharPos> => {
  const max = offsetOption.getOr(text.length);
  for (let i = max - 1; i >= 0; i--) {
    if (text.charAt(i) !== Unicode.zeroWidth) {
      return Option.some(locate(text, i));
    }
  }
  return Option.none<CharPos>();
};

const next = (text: string, offsetOption: Option<number>): Option<CharPos> => {
  const min = offsetOption.getOr(0);
  for (let i = min + 1; i < text.length; i++) {
    if (text.charAt(i) !== Unicode.zeroWidth) {
      return Option.some(locate(text, i));
    }
  }
  return Option.none<CharPos>();
};

const rfind = (str: string, regex: RegExp): Option<number> => {
  regex.lastIndex = -1;
  const reversed = Arr.reverse(str).join('');
  const match = reversed.match(regex);
  return match !== undefined && match !== null && match.index !== undefined && match.index >= 0 ? Option.some((reversed.length - 1) - match.index) : Option.none<number>();
};

const lfind = (str: string, regex: RegExp): Option<number> => {
  regex.lastIndex = -1;
  const match = str.match(regex);
  return match !== undefined && match !== null && match.index !== undefined && match.index >= 0 ? Option.some(match.index) : Option.none<number>();
};

export const TextSearch = {
  previous,
  next,
  rfind,
  lfind
};
{'fraction_non_alphanumeric': 0.17786561264822134, 'fraction_numerical': 0.005472788081483734, 'mean_word_length': 3.845360824742268, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 34, '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': '2274236', 'n_tokens_mistral': 1103, 'n_tokens_neox': 1086, 'n_words': 288}