📄 File detail

hooks/useArrowKeyHistory.tsx

🧩 .tsx📏 229 lines💾 34,135 bytes📝 text
← Back to All Files

🎯 Use case

This file lives under “hooks/”, which covers reusable UI or integration hooks. On the API surface it exposes HistoryMode and useArrowKeyHistory — mainly functions, hooks, or classes. Dependencies touch React UI and src. It composes internal code from components, history, ink, types, and utils (relative imports).

Generated from folder role, exports, dependency roots, and inline comments — not hand-reviewed for every path.

🧠 Inline summary

import React, { useCallback, useRef, useState } from 'react'; import { getModeFromInput } from 'src/components/PromptInput/inputModes.js'; import { useNotifications } from 'src/context/notifications.js'; import { ConfigurableShortcutHint } from '../components/ConfigurableShortcutHint.js'; import { FOOTER_TEMPORARY_STATUS_TIMEOUT } from '../components/PromptInput/Notifications.js';

📤 Exports (heuristic)

  • HistoryMode
  • useArrowKeyHistory

📚 External import roots

Package roots from from "…" (relative paths omitted).

  • react
  • src

🖥️ Source preview

import React, { useCallback, useRef, useState } from 'react';
import { getModeFromInput } from 'src/components/PromptInput/inputModes.js';
import { useNotifications } from 'src/context/notifications.js';
import { ConfigurableShortcutHint } from '../components/ConfigurableShortcutHint.js';
import { FOOTER_TEMPORARY_STATUS_TIMEOUT } from '../components/PromptInput/Notifications.js';
import { getHistory } from '../history.js';
import { Text } from '../ink.js';
import type { PromptInputMode } from '../types/textInputTypes.js';
import type { HistoryEntry, PastedContent } from '../utils/config.js';
export type HistoryMode = PromptInputMode;

// Load history entries in chunks to reduce disk reads on rapid keypresses
const HISTORY_CHUNK_SIZE = 10;

// Shared state for batching concurrent load requests into a single disk read
// Mode filter is included to ensure we don't mix filtered and unfiltered caches
let pendingLoad: Promise<HistoryEntry[]> | null = null;
let pendingLoadTarget = 0;
let pendingLoadModeFilter: HistoryMode | undefined = undefined;
async function loadHistoryEntries(minCount: number, modeFilter?: HistoryMode): Promise<HistoryEntry[]> {
  // Round up to next chunk to avoid repeated small reads
  const target = Math.ceil(minCount / HISTORY_CHUNK_SIZE) * HISTORY_CHUNK_SIZE;

  // If a load is already pending with the same mode filter and will satisfy our needs, wait for it
  if (pendingLoad && pendingLoadTarget >= target && pendingLoadModeFilter === modeFilter) {
    return pendingLoad;
  }

  // If a load is pending but won't satisfy our needs or has different filter, we need to wait for it
  // to complete first, then start a new one (can't interrupt an ongoing read)
  if (pendingLoad) {
    await pendingLoad;
  }

  // Start a new load
  pendingLoadTarget = target;
  pendingLoadModeFilter = modeFilter;
  pendingLoad = (async () => {
    const entries: HistoryEntry[] = [];
    let loaded = 0;
    for await (const entry of getHistory()) {
      // If mode filter is specified, only include entries that match the mode
      if (modeFilter) {
        const entryMode = getModeFromInput(entry.display);
        if (entryMode !== modeFilter) {
          continue;
        }
      }
      entries.push(entry);
      loaded++;
      if (loaded >= pendingLoadTarget) break;
    }
    return entries;
  })();
  try {
    return await pendingLoad;
  } finally {
    pendingLoad = null;
    pendingLoadTarget = 0;
    pendingLoadModeFilter = undefined;
  }
}
export function useArrowKeyHistory(onSetInput: (value: string, mode: HistoryMode, pastedContents: Record<number, PastedContent>) => void, currentInput: string, pastedContents: Record<number, PastedContent>, setCursorOffset?: (offset: number) => void, currentMode?: HistoryMode): {
  historyIndex: number;
  setHistoryIndex: (index: number) => void;
  onHistoryUp: () => void;
  onHistoryDown: () => boolean;
  resetHistory: () => void;
  dismissSearchHint: () => void;
} {
  const [historyIndex, setHistoryIndex] = useState(0);
  const [lastShownHistoryEntry, setLastShownHistoryEntry] = useState<(HistoryEntry & {
    mode?: HistoryMode;
  }) | undefined>(undefined);
  const hasShownSearchHintRef = useRef(false);
  const {
    addNotification,
    removeNotification
  } = useNotifications();

  // Cache loaded history entries
  const historyCache = useRef<HistoryEntry[]>([]);
  // Track which mode filter the cache was loaded with
  const historyCacheModeFilter = useRef<HistoryMode | undefined>(undefined);

  // Synchronous tracker for history index to avoid stale closure issues
  // React state updates are async, so rapid keypresses can see stale values
  const historyIndexRef = useRef(0);

  // Track the mode filter that was active when history navigation started
  // This is set on the first arrow press and stays fixed until reset
  const initialModeFilterRef = useRef<HistoryMode | undefined>(undefined);

  // Refs to track current input values for draft preservation
  // These ensure we capture the draft with the latest values, not stale closure values
  const currentInputRef = useRef(currentInput);
  const pastedContentsRef = useRef(pastedContents);
  const currentModeRef = useRef(currentMode);

  // Keep refs in sync with props (synchronous update on each render)
  currentInputRef.current = currentInput;
  pastedContentsRef.current = pastedContents;
  currentModeRef.current = currentMode;
  const setInputWithCursor = useCallback((value: string, mode: HistoryMode, contents: Record<number, PastedContent>, cursorToStart = false): void => {
    onSetInput(value, mode, contents);
    setCursorOffset?.(cursorToStart ? 0 : value.length);
  }, [onSetInput, setCursorOffset]);
  const updateInput = useCallback((input: HistoryEntry | undefined, cursorToStart_0 = false): void => {
    if (!input || !input.display) return;
    const mode_0 = getModeFromInput(input.display);
    const value_0 = mode_0 === 'bash' ? input.display.slice(1) : input.display;
    setInputWithCursor(value_0, mode_0, input.pastedContents ?? {}, cursorToStart_0);
  }, [setInputWithCursor]);
  const showSearchHint = useCallback((): void => {
    addNotification({
      key: 'search-history-hint',
      jsx: <Text dimColor>
          <ConfigurableShortcutHint action="history:search" context="Global" fallback="ctrl+r" description="search history" />
        </Text>,
      priority: 'immediate',
      timeoutMs: FOOTER_TEMPORARY_STATUS_TIMEOUT
    });
  }, [addNotification]);
  const onHistoryUp = useCallback((): void => {
    // Capture and increment synchronously to handle rapid keypresses
    const targetIndex = historyIndexRef.current;
    historyIndexRef.current++;
    const inputAtPress = currentInputRef.current;
    const pastedContentsAtPress = pastedContentsRef.current;
    const modeAtPress = currentModeRef.current;
    if (targetIndex === 0) {
      initialModeFilterRef.current = modeAtPress === 'bash' ? modeAtPress : undefined;

      // Save draft synchronously using refs for the latest values
      // This ensures we capture the draft before any async operations or re-renders
      const hasInput = inputAtPress.trim() !== '';
      setLastShownHistoryEntry(hasInput ? {
        display: inputAtPress,
        pastedContents: pastedContentsAtPress,
        mode: modeAtPress
      } : undefined);
    }
    const modeFilter = initialModeFilterRef.current;
    void (async () => {
      const neededCount = targetIndex + 1; // How many entries we need

      // If mode filter changed, invalidate cache
      if (historyCacheModeFilter.current !== modeFilter) {
        historyCache.current = [];
        historyCacheModeFilter.current = modeFilter;
        historyIndexRef.current = 0;
      }

      // Load more entries if needed
      if (historyCache.current.length < neededCount) {
        // Batches concurrent requests - rapid keypresses share a single disk read
        const entries = await loadHistoryEntries(neededCount, modeFilter);
        // Only update cache if we loaded more than currently cached
        // (handles race condition where multiple loads complete out of order)
        if (entries.length > historyCache.current.length) {
          historyCache.current = entries;
        }
      }

      // Check if we can navigate
      if (targetIndex >= historyCache.current.length) {
        // Rollback the ref since we can't navigate
        historyIndexRef.current--;
        // Keep the draft intact - user stays on their current input
        return;
      }
      const newIndex = targetIndex + 1;
      setHistoryIndex(newIndex);
      updateInput(historyCache.current[targetIndex], true);

      // Show hint once per session after navigating through 2 history entries
      if (newIndex >= 2 && !hasShownSearchHintRef.current) {
        hasShownSearchHintRef.current = true;
        showSearchHint();
      }
    })();
  }, [updateInput, showSearchHint]);
  const onHistoryDown = useCallback((): boolean => {
    // Use the ref for consistent reads
    const currentIndex = historyIndexRef.current;
    if (currentIndex > 1) {
      historyIndexRef.current--;
      setHistoryIndex(currentIndex - 1);
      updateInput(historyCache.current[currentIndex - 2]);
    } else if (currentIndex === 1) {
      historyIndexRef.current = 0;
      setHistoryIndex(0);
      if (lastShownHistoryEntry) {
        // Restore the draft with its saved mode if available
        const savedMode = lastShownHistoryEntry.mode;
        if (savedMode) {
          setInputWithCursor(lastShownHistoryEntry.display, savedMode, lastShownHistoryEntry.pastedContents ?? {});
        } else {
          updateInput(lastShownHistoryEntry);
        }
      } else {
        // When in filtered mode, stay in that mode when clearing input
        setInputWithCursor('', initialModeFilterRef.current ?? 'prompt', {});
      }
    }
    return currentIndex <= 0;
  }, [lastShownHistoryEntry, updateInput, setInputWithCursor]);
  const resetHistory = useCallback((): void => {
    setLastShownHistoryEntry(undefined);
    setHistoryIndex(0);
    historyIndexRef.current = 0;
    initialModeFilterRef.current = undefined;
    removeNotification('search-history-hint');
    historyCache.current = [];
    historyCacheModeFilter.current = undefined;
  }, [removeNotification]);
  const dismissSearchHint = useCallback((): void => {
    removeNotification('search-history-hint');
  }, [removeNotification]);
  return {
    historyIndex,
    setHistoryIndex,
    onHistoryUp,
    onHistoryDown,
    resetHistory,
    dismissSearchHint
  };
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUNhbGxiYWNrIiwidXNlUmVmIiwidXNlU3RhdGUiLCJnZXRNb2RlRnJvbUlucHV0IiwidXNlTm90aWZpY2F0aW9ucyIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkZPT1RFUl9URU1QT1JBUllfU1RBVFVTX1RJTUVPVVQiLCJnZXRIaXN0b3J5IiwiVGV4dCIsIlByb21wdElucHV0TW9kZSIsIkhpc3RvcnlFbnRyeSIsIlBhc3RlZENvbnRlbnQiLCJIaXN0b3J5TW9kZSIsIkhJU1RPUllfQ0hVTktfU0laRSIsInBlbmRpbmdMb2FkIiwiUHJvbWlzZSIsInBlbmRpbmdMb2FkVGFyZ2V0IiwicGVuZGluZ0xvYWRNb2RlRmlsdGVyIiwidW5kZWZpbmVkIiwibG9hZEhpc3RvcnlFbnRyaWVzIiwibWluQ291bnQiLCJtb2RlRmlsdGVyIiwidGFyZ2V0IiwiTWF0aCIsImNlaWwiLCJlbnRyaWVzIiwibG9hZGVkIiwiZW50cnkiLCJlbnRyeU1vZGUiLCJkaXNwbGF5IiwicHVzaCIsInVzZUFycm93S2V5SGlzdG9yeSIsIm9uU2V0SW5wdXQiLCJ2YWx1ZSIsIm1vZGUiLCJwYXN0ZWRDb250ZW50cyIsIlJlY29yZCIsImN1cnJlbnRJbnB1dCIsInNldEN1cnNvck9mZnNldCIsIm9mZnNldCIsImN1cnJlbnRNb2RlIiwiaGlzdG9yeUluZGV4Iiwic2V0SGlzdG9yeUluZGV4IiwiaW5kZXgiLCJvbkhpc3RvcnlVcCIsIm9uSGlzdG9yeURvd24iLCJyZXNldEhpc3RvcnkiLCJkaXNtaXNzU2VhcmNoSGludCIsImxhc3RTaG93bkhpc3RvcnlFbnRyeSIsInNldExhc3RTaG93bkhpc3RvcnlFbnRyeSIsImhhc1Nob3duU2VhcmNoSGludFJlZiIsImFkZE5vdGlmaWNhdGlvbiIsInJlbW92ZU5vdGlmaWNhdGlvbiIsImhpc3RvcnlDYWNoZSIsImhpc3RvcnlDYWNoZU1vZGVGaWx0ZXIiLCJoaXN0b3J5SW5kZXhSZWYiLCJpbml0aWFsTW9kZUZpbHRlclJlZiIsImN1cnJlbnRJbnB1dFJlZiIsInBhc3RlZENvbnRlbnRzUmVmIiwiY3VycmVudE1vZGVSZWYiLCJjdXJyZW50Iiwic2V0SW5wdXRXaXRoQ3Vyc29yIiwiY29udGVudHMiLCJjdXJzb3JUb1N0YXJ0IiwibGVuZ3RoIiwidXBkYXRlSW5wdXQiLCJpbnB1dCIsInNsaWNlIiwic2hvd1NlYXJjaEhpbnQiLCJrZXkiLCJqc3giLCJwcmlvcml0eSIsInRpbWVvdXRNcyIsInRhcmdldEluZGV4IiwiaW5wdXRBdFByZXNzIiwicGFzdGVkQ29udGVudHNBdFByZXNzIiwibW9kZUF0UHJlc3MiLCJoYXNJbnB1dCIsInRyaW0iLCJuZWVkZWRDb3VudCIsIm5ld0luZGV4IiwiY3VycmVudEluZGV4Iiwic2F2ZWRNb2RlIl0sInNvdXJjZXMiOlsidXNlQXJyb3dLZXlIaXN0b3J5LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QsIHsgdXNlQ2FsbGJhY2ssIHVzZVJlZiwgdXNlU3RhdGUgfSBmcm9tICdyZWFjdCdcbmltcG9ydCB7IGdldE1vZGVGcm9tSW5wdXQgfSBmcm9tICdzcmMvY29tcG9uZW50cy9Qcm9tcHRJbnB1dC9pbnB1dE1vZGVzLmpzJ1xuaW1wb3J0IHsgdXNlTm90aWZpY2F0aW9ucyB9IGZyb20gJ3NyYy9jb250ZXh0L25vdGlmaWNhdGlvbnMuanMnXG5pbXBvcnQgeyBDb25maWd1cmFibGVTaG9ydGN1dEhpbnQgfSBmcm9tICcuLi9jb21wb25lbnRzL0NvbmZpZ3VyYWJsZVNob3J0Y3V0SGludC5qcydcbmltcG9ydCB7IEZPT1RFUl9URU1QT1JBUllfU1RBVFVTX1RJTUVPVVQgfSBmcm9tICcuLi9jb21wb25lbnRzL1Byb21wdElucHV0L05vdGlmaWNhdGlvbnMuanMnXG5pbXBvcnQgeyBnZXRIaXN0b3J5IH0gZnJvbSAnLi4vaGlzdG9yeS5qcydcbmltcG9ydCB7IFRleHQgfSBmcm9tICcuLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IFByb21wdElucHV0TW9kZSB9IGZyb20gJy4uL3R5cGVzL3RleHRJbnB1dFR5cGVzLmpzJ1xuaW1wb3J0IHR5cGUgeyBIaXN0b3J5RW50cnksIFBhc3RlZENvbnRlbnQgfSBmcm9tICcuLi91dGlscy9jb25maWcuanMnXG5cbmV4cG9ydCB0eXBlIEhpc3RvcnlNb2RlID0gUHJvbXB0SW5wdXRNb2RlXG5cbi8vIExvYWQgaGlzdG9yeSBlbnRyaWVzIGluIGNodW5rcyB0byByZWR1Y2UgZGlzayByZWFkcyBvbiByYXBpZCBrZXlwcmVzc2VzXG5jb25zdCBISVNUT1JZX0NIVU5LX1NJWkUgPSAxMFxuXG4vLyBTaGFyZWQgc3RhdGUgZm9yIGJhdGNoaW5nIGNvbmN1cnJlbnQgbG9hZCByZXF1ZXN0cyBpbnRvIGEgc2luZ2xlIGRpc2sgcmVhZFxuLy8gTW9kZSBmaWx0ZXIgaXMgaW5jbHVkZWQgdG8gZW5zdXJlIHdlIGRvbid0IG1peCBmaWx0ZXJlZCBhbmQgdW5maWx0ZXJlZCBjYWNoZXNcbmxldCBwZW5kaW5nTG9hZDogUHJvbWlzZTxIaXN0b3J5RW50cnlbXT4gfCBudWxsID0gbnVsbFxubGV0IHBlbmRpbmdMb2FkVGFyZ2V0ID0gMFxubGV0IHBlbmRpbmdMb2FkTW9kZUZpbHRlcjogSGlzdG9yeU1vZGUgfCB1bmRlZmluZWQgPSB1bmRlZmluZWRcblxuYXN5bmMgZnVuY3Rpb24gbG9hZEhpc3RvcnlFbnRyaWVzKFxuICBtaW5Db3VudDogbnVtYmVyLFxuICBtb2RlRmlsdGVyPzogSGlzdG9yeU1vZGUsXG4pOiBQcm9taXNlPEhpc3RvcnlFbnRyeVtdPiB7XG4gIC8vIFJvdW5kIHVwIHRvIG5leHQgY2h1bmsgdG8gYXZvaWQgcmVwZWF0ZWQgc21hbGwgcmVhZHNcbiAgY29uc3QgdGFyZ2V0ID0gTWF0aC5jZWlsKG1pbkNvdW50IC8gSElTVE9SWV9DSFVOS19TSVpFKSAqIEhJU1RPUllfQ0hVTktfU0laRVxuXG4gIC8vIElmIGEgbG9hZCBpcyBhbHJlYWR5IHBlbmRpbmcgd2l0aCB0aGUgc2FtZSBtb2RlIGZpbHRlciBhbmQgd2lsbCBzYXRpc2Z5IG91ciBuZWVkcywgd2FpdCBmb3IgaXRcbiAgaWYgKFxuICAgIHBlbmRpbmdMb2FkICYmXG4gICAgcGVuZGluZ0xvYWRUYXJnZXQgPj0gdGFyZ2V0ICYmXG4gICAgcGVuZGluZ0xvYWRNb2RlRmlsdGVyID09PSBtb2RlRmlsdGVyXG4gICkge1xuICAgIHJldHVybiBwZW5kaW5nTG9hZFxuICB9XG5cbiAgLy8gSWYgYSBsb2FkIGlzIHBlbmRpbmcgYnV0IHdvbid0IHNhdGlzZnkgb3VyIG5lZWRzIG9yIGhhcyBkaWZmZXJlbnQgZmlsdGVyLCB3ZSBuZWVkIHRvIHdhaXQgZm9yIGl0XG4gIC8vIHRvIGNvbXBsZXRlIGZpcnN0LCB0aGVuIHN0YXJ0IGEgbmV3IG9uZSAoY2FuJ3QgaW50ZXJydXB0IGFuIG9uZ29pbmcgcmVhZClcbiAgaWYgKHBlbmRpbmdMb2FkKSB7XG4gICAgYXdhaXQgcGVuZGluZ0xvYWRcbiAgfVxuXG4gIC8vIFN0YXJ0IGEgbmV3IGxvYWRcbiAgcGVuZGluZ0xvYWRUYXJnZXQgPSB0YXJnZXRcbiAgcGVuZGluZ0xvYWRNb2RlRmlsdGVyID0gbW9kZUZpbHRlclxuICBwZW5kaW5nTG9hZCA9IChhc3luYyAoKSA9PiB7XG4gICAgY29uc3QgZW50cmllczogSGlzdG9yeUVudHJ5W10gPSBbXVxuICAgIGxldCBsb2FkZWQgPSAwXG4gICAgZm9yIGF3YWl0IChjb25zdCBlbnRyeSBvZiBnZXRIaXN0b3J5KCkpIHtcbiAgICAgIC8vIElmIG1vZGUgZmlsdGVyIGlzIHNwZWNpZmllZCwgb25seSBpbmNsdWRlIGVudHJpZXMgdGhhdCBtYXRjaCB0aGUgbW9kZVxuICAgICAgaWYgKG1vZGVGaWx0ZXIpIHtcbiAgICAgICAgY29uc3QgZW50cnlNb2RlID0gZ2V0TW9kZUZyb21JbnB1dChlbnRyeS5kaXNwbGF5KVxuICAgICAgICBpZiAoZW50cnlNb2RlICE9PSBtb2RlRmlsdGVyKSB7XG4gICAgICAgICAgY29udGludWVcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZW50cmllcy5wdXNoKGVudHJ5KVxuICAgICAgbG9hZGVkKytcbiAgICAgIGlmIChsb2FkZWQgPj0gcGVuZGluZ0xvYWRUYXJnZXQpIGJyZWFrXG4gICAgfVxuICAgIHJldHVybiBlbnRyaWVzXG4gIH0pKClcblxuICB0cnkge1xuICAgIHJldHVybiBhd2FpdCBwZW5kaW5nTG9hZFxuICB9IGZpbmFsbHkge1xuICAgIHBlbmRpbmdMb2FkID0gbnVsbFxuICAgIHBlbmRpbmdMb2FkVGFyZ2V0ID0gMFxuICAgIHBlbmRpbmdMb2FkTW9kZUZpbHRlciA9IHVuZGVmaW5lZFxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB1c2VBcnJvd0tleUhpc3RvcnkoXG4gIG9uU2V0SW5wdXQ6IChcbiAgICB2YWx1ZTogc3RyaW5nLFxuICAgIG1vZGU6IEhpc3RvcnlNb2RlLFxuICAgIHBhc3RlZENvbnRlbnRzOiBSZWNvcmQ8bnVtYmVyLCBQYXN0ZWRDb250ZW50PixcbiAgKSA9PiB2b2lkLFxuICBjdXJyZW50SW5wdXQ6IHN0cmluZyxcbiAgcGFzdGVkQ29udGVudHM6IFJlY29yZDxudW1iZXIsIFBhc3RlZENvbnRlbnQ+LFxuICBzZXRDdXJzb3JPZmZzZXQ/OiAob2Zmc2V0OiBudW1iZXIpID0+IHZvaWQsXG4gIGN1cnJlbnRNb2RlPzogSGlzdG9yeU1vZGUsXG4pOiB7XG4gIGhpc3RvcnlJbmRleDogbnVtYmVyXG4gIHNldEhpc3RvcnlJbmRleDogKGluZGV4OiBudW1iZXIpID0+IHZvaWRcbiAgb25IaXN0b3J5VXA6ICgpID0+IHZvaWRcbiAgb25IaXN0b3J5RG93bjogKCkgPT4gYm9vbGVhblxuICByZXNldEhpc3Rvcnk6ICgpID0+IHZvaWRcbiAgZGlzbWlzc1NlYXJjaEhpbnQ6ICgpID0+IHZvaWRcbn0ge1xuICBjb25zdCBbaGlzdG9yeUluZGV4LCBzZXRIaXN0b3J5SW5kZXhdID0gdXNlU3RhdGUoMClcbiAgY29uc3QgW2xhc3RTaG93bkhpc3RvcnlFbnRyeSwgc2V0TGFzdFNob3duSGlzdG9yeUVudHJ5XSA9IHVzZVN0YXRlPFxuICAgIChIaXN0b3J5RW50cnkgJiB7IG1vZGU/OiBIaXN0b3J5TW9kZSB9KSB8IHVuZGVmaW5lZFxuICA+KHVuZGVmaW5lZClcbiAgY29uc3QgaGFzU2hvd25TZWFyY2hIaW50UmVmID0gdXNlUmVmKGZhbHNlKVxuICBjb25zdCB7IGFkZE5vdGlmaWNhdGlvbiwgcmVtb3ZlTm90aWZpY2F0aW9uIH0gPSB1c2VOb3RpZmljYXRpb25zKClcblxuICAvLyBDYWNoZSBsb2FkZWQgaGlzdG9yeSBlbnRyaWVzXG4gIGNvbnN0IGhpc3RvcnlDYWNoZSA9IHVzZVJlZjxIaXN0b3J5RW50cnlbXT4oW10pXG4gIC8vIFRyYWNrIHdoaWNoIG1vZGUgZmlsdGVyIHRoZSBjYWNoZSB3YXMgbG9hZGVkIHdpdGhcbiAgY29uc3QgaGlzdG9yeUNhY2hlTW9kZUZpbHRlciA9IHVzZVJlZjxIaXN0b3J5TW9kZSB8IHVuZGVmaW5lZD4odW5kZWZpbmVkKVxuXG4gIC8vIFN5bmNocm9ub3VzIHRyYWNrZXIgZm9yIGhpc3RvcnkgaW5kZXggdG8gYXZvaWQgc3RhbGUgY2xvc3VyZSBpc3N1ZXNcbiAgLy8gUmVhY3Qgc3RhdGUgdXBkYXRlcyBhcmUgYXN5bmMsIHNvIHJhcGlkIGtleXByZXNzZXMgY2FuIHNlZSBzdGFsZSB2YWx1ZXNcbiAgY29uc3QgaGlzdG9yeUluZGV4UmVmID0gdXNlUmVmKDApXG5cbiAgLy8gVHJhY2sgdGhlIG1vZGUgZmlsdGVyIHRoYXQgd2FzIGFjdGl2ZSB3aGVuIGhpc3RvcnkgbmF2aWdhdGlvbiBzdGFydGVkXG4gIC8vIFRoaXMgaXMgc2V0IG9uIHRoZSBmaXJzdCBhcnJvdyBwcmVzcyBhbmQgc3RheXMgZml4ZWQgdW50aWwgcmVzZXRcbiAgY29uc3QgaW5pdGlhbE1vZGVGaWx0ZXJSZWYgPSB1c2VSZWY8SGlzdG9yeU1vZGUgfCB1bmRlZmluZWQ+KHVuZGVmaW5lZClcblxuICAvLyBSZWZzIHRvIHRyYWNrIGN1cnJlbnQgaW5wdXQgdmFsdWVzIGZvciBkcmFmdCBwcmVzZXJ2YXRpb25cbiAgLy8gVGhlc2UgZW5zdXJlIHdlIGNhcHR1cmUgdGhlIGRyYWZ0IHdpdGggdGhlIGxhdGVzdCB2YWx1ZXMsIG5vdCBzdGFsZSBjbG9zdXJlIHZhbHVlc1xuICBjb25zdCBjdXJyZW50SW5wdXRSZWYgPSB1c2VSZWYoY3VycmVudElucHV0KVxuICBjb25zdCBwYXN0ZWRDb250ZW50c1JlZiA9IHVzZVJlZihwYXN0ZWRDb250ZW50cylcbiAgY29uc3QgY3VycmVudE1vZGVSZWYgPSB1c2VSZWYoY3VycmVudE1vZGUpXG5cbiAgLy8gS2VlcCByZWZzIGluIHN5bmMgd2l0aCBwcm9wcyAoc3luY2hyb25vdXMgdXBkYXRlIG9uIGVhY2ggcmVuZGVyKVxuICBjdXJyZW50SW5wdXRSZWYuY3VycmVudCA9IGN1cnJlbnRJbnB1dFxuICBwYXN0ZWRDb250ZW50c1JlZi5jdXJyZW50ID0gcGFzdGVkQ29udGVudHNcbiAgY3VycmVudE1vZGVSZWYuY3VycmVudCA9IGN1cnJlbnRNb2RlXG5cbiAgY29uc3Qgc2V0SW5wdXRXaXRoQ3Vyc29yID0gdXNlQ2FsbGJhY2soXG4gICAgKFxuICAgICAgdmFsdWU6IHN0cmluZyxcbiAgICAgIG1vZGU6IEhpc3RvcnlNb2RlLFxuICAgICAgY29udGVudHM6IFJlY29yZDxudW1iZXIsIFBhc3RlZENvbnRlbnQ+LFxuICAgICAgY3Vyc29yVG9TdGFydCA9IGZhbHNlLFxuICAgICk6IHZvaWQgPT4ge1xuICAgICAgb25TZXRJbnB1dCh2YWx1ZSwgbW9kZSwgY29udGVudHMpXG4gICAgICBzZXRDdXJzb3JPZmZzZXQ/LihjdXJzb3JUb1N0YXJ0ID8gMCA6IHZhbHVlLmxlbmd0aClcbiAgICB9LFxuICAgIFtvblNldElucHV0LCBzZXRDdXJzb3JPZmZzZXRdLFxuICApXG5cbiAgY29uc3QgdXBkYXRlSW5wdXQgPSB1c2VDYWxsYmFjayhcbiAgICAoaW5wdXQ6IEhpc3RvcnlFbnRyeSB8IHVuZGVmaW5lZCwgY3Vyc29yVG9TdGFydCA9IGZhbHNlKTogdm9pZCA9PiB7XG4gICAgICBpZiAoIWlucHV0IHx8ICFpbnB1dC5kaXNwbGF5KSByZXR1cm5cblxuICAgICAgY29uc3QgbW9kZSA9IGdldE1vZGVGcm9tSW5wdXQoaW5wdXQuZGlzcGxheSlcbiAgICAgIGNvbnN0IHZhbHVlID0gbW9kZSA9PT0gJ2Jhc2gnID8gaW5wdXQuZGlzcGxheS5zbGljZSgxKSA6IGlucHV0LmRpc3BsYXlcblxuICAgICAgc2V0SW5wdXRXaXRoQ3Vyc29yKHZhbHVlLCBtb2RlLCBpbnB1dC5wYXN0ZWRDb250ZW50cyA/PyB7fSwgY3Vyc29yVG9TdGFydClcbiAgICB9LFxuICAgIFtzZXRJbnB1dFdpdGhDdXJzb3JdLFxuICApXG5cbiAgY29uc3Qgc2hvd1NlYXJjaEhpbnQgPSB1c2VDYWxsYmFjaygoKTogdm9pZCA9PiB7XG4gICAgYWRkTm90aWZpY2F0aW9uKHtcbiAgICAgIGtleTogJ3NlYXJjaC1oaXN0b3J5LWhpbnQnLFxuICAgICAganN4OiAoXG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgIGFjdGlvbj1cImhpc3Rvcnk6c2VhcmNoXCJcbiAgICAgICAgICAgIGNvbnRleHQ9XCJHbG9iYWxcIlxuICAgICAgICAgICAgZmFsbGJhY2s9XCJjdHJsK3JcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJzZWFyY2ggaGlzdG9yeVwiXG4gICAgICAgICAgLz5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgKSxcbiAgICAgIHByaW9yaXR5OiAnaW1tZWRpYXRlJyxcbiAgICAgIHRpbWVvdXRNczogRk9PVEVSX1RFTVBPUkFSWV9TVEFUVVNfVElNRU9VVCxcbiAgICB9KVxuICB9LCBbYWRkTm90aWZpY2F0aW9uXSlcblxuICBjb25zdCBvbkhpc3RvcnlVcCA9IHVzZUNhbGxiYWNrKCgpOiB2b2lkID0+IHtcbiAgICAvLyBDYXB0dXJlIGFuZCBpbmNyZW1lbnQgc3luY2hyb25vdXNseSB0byBoYW5kbGUgcmFwaWQga2V5cHJlc3Nlc1xuICAgIGNvbnN0IHRhcmdldEluZGV4ID0gaGlzdG9yeUluZGV4UmVmLmN1cnJlbnRcbiAgICBoaXN0b3J5SW5kZXhSZWYuY3VycmVudCsrXG5cbiAgICBjb25zdCBpbnB1dEF0UHJlc3MgPSBjdXJyZW50SW5wdXRSZWYuY3VycmVudFxuICAgIGNvbnN0IHBhc3RlZENvbnRlbnRzQXRQcmVzcyA9IHBhc3RlZENvbnRlbnRzUmVmLmN1cnJlbnRcbiAgICBjb25zdCBtb2RlQXRQcmVzcyA9IGN1cnJlbnRNb2RlUmVmLmN1cnJlbnRcblxuICAgIGlmICh0YXJnZXRJbmRleCA9PT0gMCkge1xuICAgICAgaW5pdGlhbE1vZGVGaWx0ZXJSZWYuY3VycmVudCA9XG4gICAgICAgIG1vZGVBdFByZXNzID09PSAnYmFzaCcgPyBtb2RlQXRQcmVzcyA6IHVuZGVmaW5lZFxuXG4gICAgICAvLyBTYXZlIGRyYWZ0IHN5bmNocm9ub3VzbHkgdXNpbmcgcmVmcyBmb3IgdGhlIGxhdGVzdCB2YWx1ZXNcbiAgICAgIC8vIFRoaXMgZW5zdXJlcyB3ZSBjYXB0dXJlIHRoZSBkcmFmdCBiZWZvcmUgYW55IGFzeW5jIG9wZXJhdGlvbnMgb3IgcmUtcmVuZGVyc1xuICAgICAgY29uc3QgaGFzSW5wdXQgPSBpbnB1dEF0UHJlc3MudHJpbSgpICE9PSAnJ1xuICAgICAgc2V0TGFzdFNob3duSGlzdG9yeUVudHJ5KFxuICAgICAgICBoYXNJbnB1dFxuICAgICAgICAgID8ge1xuICAgICAgICAgICAgICBkaXNwbGF5OiBpbnB1dEF0UHJlc3MsXG4gICAgICAgICAgICAgIHBhc3RlZENvbnRlbnRzOiBwYXN0ZWRDb250ZW50c0F0UHJlc3MsXG4gICAgICAgICAgICAgIG1vZGU6IG1vZGVBdFByZXNzLFxuICAgICAgICAgICAgfVxuICAgICAgICAgIDogdW5kZWZpbmVkLFxuICAgICAgKVxuICAgIH1cblxuICAgIGNvbnN0IG1vZGVGaWx0ZXIgPSBpbml0aWFsTW9kZUZpbHRlclJlZi5jdXJyZW50XG5cbiAgICB2b2lkIChhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBuZWVkZWRDb3VudCA9IHRhcmdldEluZGV4ICsgMSAvLyBIb3cgbWFueSBlbnRyaWVzIHdlIG5lZWRcblxuICAgICAgLy8gSWYgbW9kZSBmaWx0ZXIgY2hhbmdlZCwgaW52YWxpZGF0ZSBjYWNoZVxuICAgICAgaWYgKGhpc3RvcnlDYWNoZU1vZGVGaWx0ZXIuY3VycmVudCAhPT0gbW9kZUZpbHRlcikge1xuICAgICAgICBoaXN0b3J5Q2FjaGUuY3VycmVudCA9IFtdXG4gICAgICAgIGhpc3RvcnlDYWNoZU1vZGVGaWx0ZXIuY3VycmVudCA9IG1vZGVGaWx0ZXJcbiAgICAgICAgaGlzdG9yeUluZGV4UmVmLmN1cnJlbnQgPSAwXG4gICAgICB9XG5cbiAgICAgIC8vIExvYWQgbW9yZSBlbnRyaWVzIGlmIG5lZWRlZFxuICAgICAgaWYgKGhpc3RvcnlDYWNoZS5jdXJyZW50Lmxlbmd0aCA8IG5lZWRlZENvdW50KSB7XG4gICAgICAgIC8vIEJhdGNoZXMgY29uY3VycmVudCByZXF1ZXN0cyAtIHJhcGlkIGtleXByZXNzZXMgc2hhcmUgYSBzaW5nbGUgZGlzayByZWFkXG4gICAgICAgIGNvbnN0IGVudHJpZXMgPSBhd2FpdCBsb2FkSGlzdG9yeUVudHJpZXMobmVlZGVkQ291bnQsIG1vZGVGaWx0ZXIpXG4gICAgICAgIC8vIE9ubHkgdXBkYXRlIGNhY2hlIGlmIHdlIGxvYWRlZCBtb3JlIHRoYW4gY3VycmVudGx5IGNhY2hlZFxuICAgICAgICAvLyAoaGFuZGxlcyByYWNlIGNvbmRpdGlvbiB3aGVyZSBtdWx0aXBsZSBsb2FkcyBjb21wbGV0ZSBvdXQgb2Ygb3JkZXIpXG4gICAgICAgIGlmIChlbnRyaWVzLmxlbmd0aCA+IGhpc3RvcnlDYWNoZS5jdXJyZW50Lmxlbmd0aCkge1xuICAgICAgICAgIGhpc3RvcnlDYWNoZS5jdXJyZW50ID0gZW50cmllc1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIENoZWNrIGlmIHdlIGNhbiBuYXZpZ2F0ZVxuICAgICAgaWYgKHRhcmdldEluZGV4ID49IGhpc3RvcnlDYWNoZS5jdXJyZW50Lmxlbmd0aCkge1xuICAgICAgICAvLyBSb2xsYmFjayB0aGUgcmVmIHNpbmNlIHdlIGNhbid0IG5hdmlnYXRlXG4gICAgICAgIGhpc3RvcnlJbmRleFJlZi5jdXJyZW50LS1cbiAgICAgICAgLy8gS2VlcCB0aGUgZHJhZnQgaW50YWN0IC0gdXNlciBzdGF5cyBvbiB0aGVpciBjdXJyZW50IGlucHV0XG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICBjb25zdCBuZXdJbmRleCA9IHRhcmdldEluZGV4ICsgMVxuICAgICAgc2V0SGlzdG9yeUluZGV4KG5ld0luZGV4KVxuICAgICAgdXBkYXRlSW5wdXQoaGlzdG9yeUNhY2hlLmN1cnJlbnRbdGFyZ2V0SW5kZXhdLCB0cnVlKVxuXG4gICAgICAvLyBTaG93IGhpbnQgb25jZSBwZXIgc2Vzc2lvbiBhZnRlciBuYXZpZ2F0aW5nIHRocm91Z2ggMiBoaXN0b3J5IGVudHJpZXNcbiAgICAgIGlmIChuZXdJbmRleCA+PSAyICYmICFoYXNTaG93blNlYXJjaEhpbnRSZWYuY3VycmVudCkge1xuICAgICAgICBoYXNTaG93blNlYXJjaEhpbnRSZWYuY3VycmVudCA9IHRydWVcbiAgICAgICAgc2hvd1NlYXJjaEhpbnQoKVxuICAgICAgfVxuICAgIH0pKClcbiAgfSwgW3VwZGF0ZUlucHV0LCBzaG93U2VhcmNoSGludF0pXG5cbiAgY29uc3Qgb25IaXN0b3J5RG93biA9IHVzZUNhbGxiYWNrKCgpOiBib29sZWFuID0+IHtcbiAgICAvLyBVc2UgdGhlIHJlZiBmb3IgY29uc2lzdGVudCByZWFkc1xuICAgIGNvbnN0IGN1cnJlbnRJbmRleCA9IGhpc3RvcnlJbmRleFJlZi5jdXJyZW50XG4gICAgaWYgKGN1cnJlbnRJbmRleCA+IDEpIHtcbiAgICAgIGhpc3RvcnlJbmRleFJlZi5jdXJyZW50LS1cbiAgICAgIHNldEhpc3RvcnlJbmRleChjdXJyZW50SW5kZXggLSAxKVxuICAgICAgdXBkYXRlSW5wdXQoaGlzdG9yeUNhY2hlLmN1cnJlbnRbY3VycmVudEluZGV4IC0gMl0pXG4gICAgfSBlbHNlIGlmIChjdXJyZW50SW5kZXggPT09IDEpIHtcbiAgICAgIGhpc3RvcnlJbmRleFJlZi5jdXJyZW50ID0gMFxuICAgICAgc2V0SGlzdG9yeUluZGV4KDApXG4gICAgICBpZiAobGFzdFNob3duSGlzdG9yeUVudHJ5KSB7XG4gICAgICAgIC8vIFJlc3RvcmUgdGhlIGRyYWZ0IHdpdGggaXRzIHNhdmVkIG1vZGUgaWYgYXZhaWxhYmxlXG4gICAgICAgIGNvbnN0IHNhdmVkTW9kZSA9IGxhc3RTaG93bkhpc3RvcnlFbnRyeS5tb2RlXG4gICAgICAgIGlmIChzYXZlZE1vZGUpIHtcbiAgICAgICAgICBzZXRJbnB1dFdpdGhDdXJzb3IoXG4gICAgICAgICAgICBsYXN0U2hvd25IaXN0b3J5RW50cnkuZGlzcGxheSxcbiAgICAgICAgICAgIHNhdmVkTW9kZSxcbiAgICAgICAgICAgIGxhc3RTaG93bkhpc3RvcnlFbnRyeS5wYXN0ZWRDb250ZW50cyA/PyB7fSxcbiAgICAgICAgICApXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdXBkYXRlSW5wdXQobGFzdFNob3duSGlzdG9yeUVudHJ5KVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBXaGVuIGluIGZpbHRlcmVkIG1vZGUsIHN0YXkgaW4gdGhhdCBtb2RlIHdoZW4gY2xlYXJpbmcgaW5wdXRcbiAgICAgICAgc2V0SW5wdXRXaXRoQ3Vyc29yKCcnLCBpbml0aWFsTW9kZUZpbHRlclJlZi5jdXJyZW50ID8/ICdwcm9tcHQnLCB7fSlcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGN1cnJlbnRJbmRleCA8PSAwXG4gIH0sIFtsYXN0U2hvd25IaXN0b3J5RW50cnksIHVwZGF0ZUlucHV0LCBzZXRJbnB1dFdpdGhDdXJzb3JdKVxuXG4gIGNvbnN0IHJlc2V0SGlzdG9yeSA9IHVzZUNhbGxiYWNrKCgpOiB2b2lkID0+IHtcbiAgICBzZXRMYXN0U2hvd25IaXN0b3J5RW50cnkodW5kZWZpbmVkKVxuICAgIHNldEhpc3RvcnlJbmRleCgwKVxuICAgIGhpc3RvcnlJbmRleFJlZi5jdXJyZW50ID0gMFxuICAgIGluaXRpYWxNb2RlRmlsdGVyUmVmLmN1cnJlbnQgPSB1bmRlZmluZWRcbiAgICByZW1vdmVOb3RpZmljYXRpb24oJ3NlYXJjaC1oaXN0b3J5LWhpbnQnKVxuICAgIGhpc3RvcnlDYWNoZS5jdXJyZW50ID0gW11cbiAgICBoaXN0b3J5Q2FjaGVNb2RlRmlsdGVyLmN1cnJlbnQgPSB1bmRlZmluZWRcbiAgfSwgW3JlbW92ZU5vdGlmaWNhdGlvbl0pXG5cbiAgY29uc3QgZGlzbWlzc1NlYXJjaEhpbnQgPSB1c2VDYWxsYmFjaygoKTogdm9pZCA9PiB7XG4gICAgcmVtb3ZlTm90aWZpY2F0aW9uKCdzZWFyY2gtaGlzdG9yeS1oaW50JylcbiAgfSwgW3JlbW92ZU5vdGlmaWNhdGlvbl0pXG5cbiAgcmV0dXJuIHtcbiAgICBoaXN0b3J5SW5kZXgsXG4gICAgc2V0SGlzdG9yeUluZGV4LFxuICAgIG9uSGlzdG9yeVVwLFxuICAgIG9uSGlzdG9yeURvd24sXG4gICAgcmVzZXRIaXN0b3J5LFxuICAgIGRpc21pc3NTZWFyY2hIaW50LFxuICB9XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU9BLEtBQUssSUFBSUMsV0FBVyxFQUFFQyxNQUFNLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQzVELFNBQVNDLGdCQUFnQixRQUFRLDBDQUEwQztBQUMzRSxTQUFTQyxnQkFBZ0IsUUFBUSw4QkFBOEI7QUFDL0QsU0FBU0Msd0JBQXdCLFFBQVEsMkNBQTJDO0FBQ3BGLFNBQVNDLCtCQUErQixRQUFRLDRDQUE0QztBQUM1RixTQUFTQyxVQUFVLFFBQVEsZUFBZTtBQUMxQyxTQUFTQyxJQUFJLFFBQVEsV0FBVztBQUNoQyxjQUFjQyxlQUFlLFFBQVEsNEJBQTRCO0FBQ2pFLGNBQWNDLFlBQVksRUFBRUMsYUFBYSxRQUFRLG9CQUFvQjtBQUVyRSxPQUFPLEtB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