<commit_before>/*
 * oultine.ts
 *
 * Copyright (C) 2020 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

import { Plugin, PluginKey, EditorState, Transaction } from 'prosemirror-state';
import { Schema, Node as ProsemirrorNode } from 'prosemirror-model';
import { EditorView } from 'prosemirror-view';

import { NodeWithPos } from 'prosemirror-utils';

import { Extension } from '../api/extension';
import { transactionsHaveChange, kSetMarkdownTransaction } from '../api/transaction';
import { findTopLevelBodyNodes } from '../api/node';
import { uuidv4 } from '../api/util';
import { EditorOutlineItem, EditorOutlineItemType, EditorOutline, isOutlineNode, getEditingOutlineLocation, getDocumentOutline } from '../api/outline';
import { navigateToPos } from '../api/navigation';
import { ProsemirrorCommand, EditorCommandId } from '../api/command';

const kOutlineIdsTransaction = 'OutlineIds';

const plugin = new PluginKey<EditorOutline>('outline');

const extension: Extension = {
  appendTransaction: (schema: Schema) => {
    return [
      {
        name: 'outline',
        filter: hasOutlineIdsTransaction,
        nodeFilter: isOutlineNode,
        append: (tr: Transaction) => {
          const usedIds = new Set<string>();
          const needsOutlineId = (node: ProsemirrorNode) => {
            if (isOutlineNode(node)) {
              if (node.attrs.navigation_id === null) {
                return true;
              } else if (usedIds.has(node.attrs.navigation_id)) {
                return true;
              } else {
                usedIds.add(node.attrs.navigation_id);
                return false;
              }
            } else {
              return false;
            }
          };
          const needsOutlineIdNodes = findTopLevelBodyNodes(tr.doc, needsOutlineId);
          needsOutlineIdNodes.forEach(outlineId => {
            tr.setNodeMarkup(outlineId.pos, outlineId.node.type, {
              ...outlineId.node.attrs,
              navigation_id: uuidv4(),
            });
            tr.setMeta(kOutlineIdsTransaction, true);
          });
        },
      },
    ];
  },

  commands: () => {
    return [
      new ProsemirrorCommand(EditorCommandId.GoToNextSection, ['Mod-PageDown'], goToSectionCommand('next')),
      new ProsemirrorCommand(EditorCommandId.GoToPreviousSection, ['Mod-PageUp'], goToSectionCommand('previous')),
    ];
  },

  plugins: (schema: Schema) => {
    return [
      new Plugin<EditorOutline>({
        key: plugin,

        // track editor outline as state changes
        state: {
          init: (_config, state: EditorState) => {
            return editorOutline(state);
          },
          apply: (tr: Transaction, value: EditorOutline, oldState: EditorState, newState: EditorState) => {
            if (transactionsAffectOutline([tr], oldState, newState)) {
              return editorOutline(newState);
            } else {
              return value; // don't need to map b/c there are no positions in the data structure
            }
          },
        },
      }),
    ];
  },
};


export function goToSectionCommand(dir: 'next' | 'previous') {
  return (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) {
    if (dispatch && view) {
      let outline = getDocumentOutline(state);
      if (dir === 'previous') {
        outline = outline.reverse();
      }
      const target = outline.find(nodeWithPos => {
        if (dir === 'next') {<commit_msg>add arrow operator for go to section command<commit_after>/*
 * oultine.ts
 *
 * Copyright (C) 2020 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

import { Plugin, PluginKey, EditorState, Transaction } from 'prosemirror-state';
import { Schema, Node as ProsemirrorNode } from 'prosemirror-model';
import { EditorView } from 'prosemirror-view';

import { NodeWithPos } from 'prosemirror-utils';

import { Extension } from '../api/extension';
import { transactionsHaveChange, kSetMarkdownTransaction } from '../api/transaction';
import { findTopLevelBodyNodes } from '../api/node';
import { uuidv4 } from '../api/util';
import { EditorOutlineItem, EditorOutlineItemType, EditorOutline, isOutlineNode, getEditingOutlineLocation, getDocumentOutline } from '../api/outline';
import { navigateToPos } from '../api/navigation';
import { ProsemirrorCommand, EditorCommandId } from '../api/command';

const kOutlineIdsTransaction = 'OutlineIds';

const plugin = new PluginKey<EditorOutline>('outline');

const extension: Extension = {
  appendTransaction: (schema: Schema) => {
    return [
      {
        name: 'outline',
        filter: hasOutlineIdsTransaction,
        nodeFilter: isOutlineNode,
        append: (tr: Transaction) => {
          const usedIds = new Set<string>();
          const needsOutlineId = (node: ProsemirrorNode) => {
            if (isOutlineNode(node)) {
              if (node.attrs.navigation_id === null) {
                return true;
              } else if (usedIds.has(node.attrs.navigation_id)) {
                return true;
              } else {
                usedIds.add(node.attrs.navigation_id);
                return false;
              }
            } else {
              return false;
            }
          };
          const needsOutlineIdNodes = findTopLevelBodyNodes(tr.doc, needsOutlineId);
          needsOutlineIdNodes.forEach(outlineId => {
            tr.setNodeMarkup(outlineId.pos, outlineId.node.type, {
              ...outlineId.node.attrs,
              navigation_id: uuidv4(),
            });
            tr.setMeta(kOutlineIdsTransaction, true);
          });
        },
      },
    ];
  },

  commands: () => {
    return [
      new ProsemirrorCommand(EditorCommandId.GoToNextSection, ['Mod-PageDown'], goToSectionCommand('next')),
      new ProsemirrorCommand(EditorCommandId.GoToPreviousSection, ['Mod-PageUp'], goToSectionCommand('previous')),
    ];
  },

  plugins: (schema: Schema) => {
    return [
      new Plugin<EditorOutline>({
        key: plugin,

        // track editor outline as state changes
        state: {
          init: (_config, state: EditorState) => {
            return editorOutline(state);
          },
          apply: (tr: Transaction, value: EditorOutline, oldState: EditorState, newState: EditorState) => {
            if (transactionsAffectOutline([tr], oldState, newState)) {
              return editorOutline(newState);
            } else {
              return value; // don't need to map b/c there are no positions in the data structure
            }
          },
        },
      }),
    ];
  },
};


export function goToSectionCommand(dir: 'next' | 'previous') {
  return (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => {
    if (dispatch && view) {
      let outline = getDocumentOutline(state);
      if (dir === 'previous') {
        outline = outline.reverse();
      }
      const target = outline.find(nodeWithPos => {
        if (dir === 'next') {
{'fraction_non_alphanumeric': 0.11610297829379101, 'fraction_numerical': 0.002271580010095911, 'mean_word_length': 2.5682125168842864, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '7146064', 'n_tokens_mistral': 2217, 'n_tokens_neox': 2170, 'n_words': 587}