<commit_before>import isEqual from '../util/isEqual';
import Editor, { EditorChangeEvent } from '../Editor';
import { getSelection, setSelection } from '../rendering/selection';
import { getLineNodeStart } from '../rendering/rendering';
import { DecorationsModule } from './decorations';


export function selection(editor: Editor) {
  let rootDocument: Document;
  let rootWindow: Window;
  let paused = false;

  function onSelectionChange() {
    if (paused) return;
    const selection = getSelection(editor);
    const { doc } = editor;
    if (!isEqual(doc.selection, selection)) {
      if (selection && selection[0] === selection[1] && selection[0] >= doc.length) {
        return; // Assuming this is a text composition at the end of the document, allow the entry
      }
      editor.select(selection);
    }
  }

  function renderSelection() {
    if (paused) return;
    setSelection(editor, editor.doc.selection);
  }

  function onDecorate() {
    const { doc, typeset: { lines }} = editor;
    const decorator = (editor.modules.decorations as DecorationsModule).getDecorator('selection');
    decorator.clear();
    const selection = doc.selection;
    if (selection) {
      doc.getLinesAt(selection).forEach(line => {
        if (line.length === 1 && lines.findByAttributes(line.attributes, true).frozen) {
          const focused = selection[0] === selection[1];
          decorator.decorateLine(doc.getLineRange(line)[0], { class: 'selected' + (focused ? ' focus' : '') });
        }
      });
    }
    decorator.apply();
  }

  function onMouseDown(event: MouseEvent) {
    // Helps select lines that are not easily selectable (e.g. <hr>)
    const start = getLineNodeStart(editor.root, event.target as Node);
    const line = start != null && editor.doc.getLineAt(start);
    const type = line && editor.typeset.lines.findByAttributes(line.attributes);
    if (start != null && line && line.length === 1 && type && type.frozen) {
      event.preventDefault();
      editor.select(start);
    }
  }

  function onChange(event: EditorChangeEvent) {
    const selection = event.doc?.selection || editor.doc.selection;
    paused = true;
    setTimeout(() => paused = false);
    setSelection(editor, selection);
  }

  function onWindowFocus() {
    editor.root.classList.toggle('window-inactive', !rootDocument.hasFocus());
  }

  function pause() {
    paused = true;
    const { selection } = editor.doc;
    const { decorations } = editor.modules as {decorations: DecorationsModule}
    if (selection && selection[0] !== selection[1] && decorations) {
      const decorator = decorations.getDecorator('pausedSelection');
      decorator.decorateText(selection, { class: 'selected' }).apply();
    }
  }

  function resume() {
    paused = false;
    const { decorations } = editor.modules as {decorations: DecorationsModule}
    if (decorations) {
      decorations.removeDecorations('pausedSelection');
    }
    setTimeout(renderSelection);
  }<commit_msg>Fix bug where decorator changes moved visible selection<commit_after>import isEqual from '../util/isEqual';
import Editor, { EditorChangeEvent } from '../Editor';
import { getSelection, setSelection } from '../rendering/selection';
import { getLineNodeStart } from '../rendering/rendering';
import { DecorationsModule } from './decorations';


export function selection(editor: Editor) {
  let rootDocument: Document;
  let rootWindow: Window;
  let paused = false;

  function onSelectionChange() {
    if (paused) return;
    const selection = getSelection(editor);
    const { doc } = editor;
    if (!isEqual(doc.selection, selection)) {
      if (selection && selection[0] === selection[1] && selection[0] >= doc.length) {
        return; // Assuming this is a text composition at the end of the document, allow the entry
      }
      editor.select(selection);
    }
  }

  function renderSelection() {
    if (paused) return;
    setSelection(editor, editor.doc.selection);
  }

  function onDecorate() {
    const { doc, typeset: { lines }} = editor;
    const decorator = (editor.modules.decorations as DecorationsModule).getDecorator('selection');
    decorator.clear();
    const selection = doc.selection;
    if (selection) {
      doc.getLinesAt(selection).forEach(line => {
        if (line.length === 1 && lines.findByAttributes(line.attributes, true).frozen) {
          const focused = selection[0] === selection[1];
          decorator.decorateLine(doc.getLineRange(line)[0], { class: 'selected' + (focused ? ' focus' : '') });
        }
      });
    }
    decorator.apply();
  }

  function onMouseDown(event: MouseEvent) {
    // Helps select lines that are not easily selectable (e.g. <hr>)
    const start = getLineNodeStart(editor.root, event.target as Node);
    const line = start != null && editor.doc.getLineAt(start);
    const type = line && editor.typeset.lines.findByAttributes(line.attributes);
    if (start != null && line && line.length === 1 && type && type.frozen) {
      event.preventDefault();
      editor.select(start);
    }
  }

  function onChange(event: EditorChangeEvent) {
    const selection = event.doc?.selection || editor.doc.selection;
    setSelection(editor, selection);
  }

  function onWindowFocus() {
    editor.root.classList.toggle('window-inactive', !rootDocument.hasFocus());
  }

  function pause() {
    paused = true;
    const { selection } = editor.doc;
    const { decorations } = editor.modules as {decorations: DecorationsModule}
    if (selection && selection[0] !== selection[1] && decorations) {
      const decorator = decorations.getDecorator('pausedSelection');
      decorator.decorateText(selection, { class: 'selected' }).apply();
    }
  }

  function resume() {
    paused = false;
    const { decorations } = editor.modules as {decorations: DecorationsModule}
    if (decorations) {
      decorations.removeDecorations('pausedSelection');
    }
    setTimeout(renderSelection);
  }
{'fraction_non_alphanumeric': 0.13620312762737516, 'fraction_numerical': 0.0033630401883302506, 'mean_word_length': 3.4520958083832336, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 10, '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': '6240981', 'n_tokens_mistral': 1716, 'n_tokens_neox': 1680, 'n_words': 418}