// src/ui/autocomplete.ts
import { spawnSync } from "child_process";
import { readdirSync, statSync } from "fs";
import { homedir } from "os";
import { basename, dirname, join } from "path";

// src/ui/fuzzy.ts
var DEFAULT_FUZZY_CONFIG = {
  gapPenalty: 3,
  consecutiveReward: 4,
  wordBoundaryReward: 12,
  lateMatchPenalty: 0.15,
  swappedTokenPenalty: 6
};
var WORD_BOUNDARY = /[\s\-_./:\\@~]/;
function locateMatchColumns(queryLower, textLower) {
  const columns = [];
  let cursor = 0;
  for (let needle = 0; needle < queryLower.length; needle++) {
    const wanted = queryLower[needle];
    let found = -1;
    while (cursor < textLower.length) {
      const here = cursor++;
      if (textLower[here] === wanted) {
        found = here;
        break;
      }
    }
    if (found < 0) {
      return null;
    }
    columns.push(found);
  }
  return columns;
}
function scoreMatchColumns(textLower, columns, config) {
  let total = 0;
  let previous = -1;
  let streak = 0;
  for (const column of columns) {
    const adjacent = previous === column - 1;
    if (adjacent) {
      streak += 1;
      total -= streak * config.consecutiveReward;
    } else {
      streak = 0;
      if (previous >= 0) {
        total += (column - previous - 1) * config.gapPenalty;
      }
    }
    const startsWord = column === 0 || WORD_BOUNDARY.test(textLower[column - 1]);
    if (startsWord) {
      total -= config.wordBoundaryReward;
    }
    total += column * config.lateMatchPenalty;
    previous = column;
  }
  return total;
}
var SequentialScanEngine = class {
  static match(queryLower, textLower, config) {
    if (queryLower.length === 0) {
      return { matches: true, score: 0 };
    }
    if (queryLower.length > textLower.length) {
      return { matches: false, score: 0 };
    }
    const columns = locateMatchColumns(queryLower, textLower);
    if (columns === null) {
      return { matches: false, score: 0 };
    }
    return { matches: true, score: scoreMatchColumns(textLower, columns, config) };
  }
};
var SequentialFuzzyStrategy = class {
  match(query, text, config) {
    return SequentialScanEngine.match(query.toLowerCase(), text.toLowerCase(), config);
  }
};
var CacheEvictionPolicy = class {
  constructor(maxSize) {
    this.maxSize = maxSize;
  }
  maxSize;
  evictIfNeeded(cache) {
    if (cache.size < this.maxSize) {
      return;
    }
    const oldest = cache.keys().next().value;
    if (oldest) {
      cache.delete(oldest);
    }
  }
};
var CachedFuzzyMatcher = class {
  constructor(strategy, config, maxSize = 2e3) {
    this.strategy = strategy;
    this.config = config;
    this.eviction = new CacheEvictionPolicy(maxSize);
  }
  strategy;
  config;
  cache = /* @__PURE__ */ new Map();
  eviction;
  match(query, text) {
    const key = `${query}${text}`;
    const cached = this.cache.get(key);
    if (cached) {
      return cached;
    }
    const result = this.strategy.match(query, text, this.config);
    this.eviction.evictIfNeeded(this.cache);
    this.cache.set(key, result);
    return result;
  }
  clear() {
    this.cache.clear();
  }
};
var FuzzyQuerySwapResolver = class {
  // When a query is a single run of letters glued to a single run of digits
  // (or vice-versa), produce the variant with the two runs swapped. Anything
  // else yields an empty string, meaning "no alternative to try".
  static toSwappedQuery(query) {
    const normalized = query.toLowerCase();
    const letterThenDigit = /^(?<letters>[a-z]+)(?<digits>[0-9]+)$/.exec(normalized);
    if (letterThenDigit) {
      const { letters = "", digits = "" } = letterThenDigit.groups ?? {};
      return `${digits}${letters}`;
    }
    const digitThenLetter = /^(?<digits>[0-9]+)(?<letters>[a-z]+)$/.exec(normalized);
    if (digitThenLetter) {
      const { letters = "", digits = "" } = digitThenLetter.groups ?? {};
      return `${letters}${digits}`;
    }
    return "";
  }
};
var FuzzyTokenMatcher = class {
  static tokenize(query) {
    const trimmed = query.trim();
    if (trimmed.length === 0) {
      return [];
    }
    return trimmed.split(/\s+/).filter((piece) => piece.length > 0);
  }
};
var defaultMatcher = new CachedFuzzyMatcher(new SequentialFuzzyStrategy(), DEFAULT_FUZZY_CONFIG);
function fuzzyMatch(query, text) {
  const direct = defaultMatcher.match(query, text);
  if (direct.matches) {
    return direct;
  }
  const alternative = FuzzyQuerySwapResolver.toSwappedQuery(query);
  if (alternative.length === 0) {
    return direct;
  }
  const swapped = defaultMatcher.match(alternative, text);
  if (!swapped.matches) {
    return direct;
  }
  return {
    matches: true,
    score: swapped.score + DEFAULT_FUZZY_CONFIG.swappedTokenPenalty
  };
}
function fuzzyFilter(items, query, getText) {
  const tokens = FuzzyTokenMatcher.tokenize(query);
  if (tokens.length === 0) {
    return items;
  }
  const ranked = [];
  for (const item of items) {
    const text = getText(item);
    let aggregate = 0;
    let qualifies = true;
    for (const token of tokens) {
      const outcome = fuzzyMatch(token, text);
      if (!outcome.matches) {
        qualifies = false;
        break;
      }
      aggregate += outcome.score;
    }
    if (qualifies) {
      ranked.push({ item, rank: aggregate });
    }
  }
  ranked.sort((left, right) => left.rank - right.rank);
  return ranked.map((entry) => entry.item);
}

// src/ui/autocomplete.ts
function walkDirectoryWithFd(baseDir, fdPath, query, maxResults) {
  const invocation = ["--base-directory", baseDir];
  invocation.push("--max-results", String(maxResults));
  invocation.push("--type", "f", "--type", "d");
  invocation.push("--full-path");
  if (query) {
    invocation.push(query);
  }
  const child = spawnSync(fdPath, invocation, {
    encoding: "utf-8",
    stdio: ["pipe", "pipe", "pipe"],
    maxBuffer: 10 * 1024 * 1024
  });
  const exitedCleanly = child.status === 0 && Boolean(child.stdout);
  if (!exitedCleanly) {
    return [];
  }
  const lines = child.stdout.trim().split("\n");
  const entries = [];
  for (const line of lines) {
    if (!line) {
      continue;
    }
    entries.push({ path: line, isDirectory: line.endsWith("/") });
  }
  return entries;
}
var SuggestionCache = class {
  store = /* @__PURE__ */ new Map();
  getOrLoad(key, loader) {
    const cached = this.store.get(key);
    if (cached) return cached;
    const loaded = loader();
    this.evictIfNeeded();
    this.store.set(key, loaded);
    return loaded;
  }
  clear() {
    this.store.clear();
  }
  evictIfNeeded() {
    if (this.store.size <= 200) return;
    const oldest = this.store.keys().next().value;
    if (oldest) this.store.delete(oldest);
  }
};
var CommandAutocompleteEngine = class {
  constructor(commands) {
    this.commands = commands;
  }
  commands;
  getSuggestions(textBeforeCursor) {
    if (!textBeforeCursor.startsWith("/")) return null;
    const spaceIndex = textBeforeCursor.indexOf(" ");
    if (spaceIndex === -1) {
      return this.completeCommandName(textBeforeCursor);
    }
    return this.completeCommandArgument(textBeforeCursor, spaceIndex);
  }
  completeCommandName(textBeforeCursor) {
    const typed = textBeforeCursor.slice(1);
    const searchable = this.commands.map((entry) => {
      const isSlash = "name" in entry;
      return {
        name: isSlash ? entry.name : entry.value,
        label: isSlash ? entry.name : entry.label,
        description: entry.description
      };
    });
    const ranked = fuzzyFilter(searchable, typed, (entry) => entry.name);
    const items = ranked.map((entry) => {
      const suggestion = { value: entry.name, label: entry.label };
      if (entry.description) {
        suggestion.description = entry.description;
      }
      return suggestion;
    });
    if (items.length === 0) return null;
    return { items, prefix: textBeforeCursor };
  }
  completeCommandArgument(textBeforeCursor, spaceIndex) {
    const commandName = textBeforeCursor.slice(1, spaceIndex);
    const argumentPrefix = textBeforeCursor.slice(spaceIndex + 1);
    const command = this.commands.find((cmd) => {
      const name = "name" in cmd ? cmd.name : cmd.value;
      return name === commandName;
    });
    if (!command || !("getArgumentCompletions" in command) || !command.getArgumentCompletions) {
      return null;
    }
    const items = command.getArgumentCompletions(argumentPrefix);
    if (!items || items.length === 0) return null;
    return { items, prefix: argumentPrefix };
  }
};
var AttachmentAutocompleteEngine = class {
  getAttachmentPrefix(textBeforeCursor) {
    const match = textBeforeCursor.match(/(?:^|[\s])(@[^\s]*)$/);
    return match?.[1] ?? null;
  }
  toQuery(prefix) {
    return prefix.slice(1);
  }
};
var PathAutocompleteEngine = class {
  constructor(basePath, fdPath) {
    this.basePath = basePath;
    this.fdPath = fdPath;
  }
  basePath;
  fdPath;
  extractPathPrefix(text, forceExtract = false) {
    const atMatch = text.match(/@([^\s]*)$/);
    if (atMatch) return atMatch[0];
    const delimiters = [" ", "	", '"', "'", "="];
    let cut = -1;
    for (const delimiter of delimiters) {
      const at = text.lastIndexOf(delimiter);
      if (at > cut) cut = at;
    }
    const fragment = cut === -1 ? text : text.slice(cut + 1);
    if (forceExtract) return fragment;
    const looksLikePath = fragment.includes("/") || fragment.startsWith(".") || fragment.startsWith("~/");
    if (looksLikePath) return fragment;
    const atFreshToken = fragment === "" && (text === "" || text.endsWith(" "));
    if (atFreshToken) return fragment;
    return null;
  }
  shouldTriggerForTab(textBeforeCursor) {
    const trimmed = textBeforeCursor.trim();
    const isBareCommandName = trimmed.startsWith("/") && !trimmed.includes(" ");
    return !isBareCommandName;
  }
  getFileSuggestions(prefix) {
    try {
      const plan = this.resolveSearchPlan(prefix);
      const entries = readdirSync(plan.searchDir, { withFileTypes: true });
      const suggestions = [];
      for (const entry of entries) {
        if (!entry.name.toLowerCase().startsWith(plan.searchPrefix.toLowerCase())) continue;
        const isDirectory = this.resolveDirectoryFlag(plan.searchDir, entry.name, entry.isDirectory(), entry.isSymbolicLink());
        const relativePath = this.toRelativePath(prefix, plan.expandedPrefix, entry.name, plan.isAtPrefix);
        suggestions.push({
          value: isDirectory ? `${relativePath}/` : relativePath,
          label: entry.name + (isDirectory ? "/" : "")
        });
      }
      return suggestions.sort((a, b) => {
        const aDir = a.value.endsWith("/");
        const bDir = b.value.endsWith("/");
        if (aDir && !bDir) return -1;
        if (!aDir && bDir) return 1;
        return a.label.localeCompare(b.label);
      });
    } catch {
      return [];
    }
  }
  getFuzzyFileSuggestions(query) {
    if (!this.fdPath) return [];
    try {
      const found = walkDirectoryWithFd(this.basePath, this.fdPath, query, 100);
      const withScores = found.map((entry) => ({
        ...entry,
        score: query ? this.scoreEntry(entry.path, query, entry.isDirectory) : 1
      }));
      const top = withScores.filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score).slice(0, 20);
      return top.map(({ path: entryPath, isDirectory }) => {
        const bare = isDirectory ? entryPath.slice(0, -1) : entryPath;
        return {
          value: `@${entryPath}`,
          label: basename(bare) + (isDirectory ? "/" : ""),
          description: bare
        };
      });
    } catch {
      return [];
    }
  }
  resolveSearchPlan(prefix) {
    const isAtPrefix = prefix.startsWith("@");
    let expandedPrefix = isAtPrefix ? prefix.slice(1) : prefix;
    if (expandedPrefix.startsWith("~")) expandedPrefix = this.expandHomePath(expandedPrefix);
    const rootLike = expandedPrefix === "" || expandedPrefix === "./" || expandedPrefix === "../" || expandedPrefix === "~" || expandedPrefix === "~/" || expandedPrefix === "/" || prefix === "@";
    if (rootLike) {
      const searchDir2 = prefix.startsWith("~") || expandedPrefix === "/" ? expandedPrefix : join(this.basePath, expandedPrefix);
      return { searchDir: searchDir2, searchPrefix: "", expandedPrefix, isAtPrefix };
    }
    if (expandedPrefix.endsWith("/")) {
      const searchDir2 = prefix.startsWith("~") || expandedPrefix.startsWith("/") ? expandedPrefix : join(this.basePath, expandedPrefix);
      return { searchDir: searchDir2, searchPrefix: "", expandedPrefix, isAtPrefix };
    }
    const dir = dirname(expandedPrefix);
    const file = basename(expandedPrefix);
    const searchDir = prefix.startsWith("~") || expandedPrefix.startsWith("/") ? dir : join(this.basePath, dir);
    return { searchDir, searchPrefix: file, expandedPrefix, isAtPrefix };
  }
  toRelativePath(prefix, expandedPrefix, name, isAtPrefix) {
    if (isAtPrefix) return this.buildAtPath(expandedPrefix, name);
    if (prefix.endsWith("/")) return prefix + name;
    if (prefix.includes("/")) {
      if (prefix.startsWith("~/")) {
        const homeRelativeDir = prefix.slice(2);
        const dir = dirname(homeRelativeDir);
        return `~/${dir === "." ? name : join(dir, name)}`;
      }
      if (prefix.startsWith("/")) {
        const dir = dirname(prefix);
        return dir === "/" ? `/${name}` : `${dir}/${name}`;
      }
      return join(dirname(prefix), name);
    }
    return prefix.startsWith("~") ? `~/${name}` : name;
  }
  buildAtPath(pathWithoutAt, name) {
    if (pathWithoutAt.endsWith("/")) {
      return `@${pathWithoutAt}${name}`;
    }
    if (pathWithoutAt.includes("/")) {
      if (pathWithoutAt.startsWith("~/")) {
        const homeRelativeDir = pathWithoutAt.slice(2);
        const dir = dirname(homeRelativeDir);
        return `@~/${dir === "." ? name : join(dir, name)}`;
      }
      return `@${join(dirname(pathWithoutAt), name)}`;
    }
    return pathWithoutAt.startsWith("~") ? `@~/${name}` : `@${name}`;
  }
  resolveDirectoryFlag(searchDir, name, isDirectory, isSymlink) {
    if (isDirectory || !isSymlink) return isDirectory;
    try {
      return statSync(join(searchDir, name)).isDirectory();
    } catch {
      return false;
    }
  }
  expandHomePath(pathValue) {
    if (pathValue.startsWith("~/")) {
      const expanded = join(homedir(), pathValue.slice(2));
      return pathValue.endsWith("/") && !expanded.endsWith("/") ? `${expanded}/` : expanded;
    }
    if (pathValue === "~") return homedir();
    return pathValue;
  }
  scoreEntry(filePath, query, isDirectory) {
    const fileName = basename(filePath).toLowerCase();
    const queryLower = query.toLowerCase();
    let score = 0;
    if (fileName === queryLower) score = 100;
    else if (fileName.startsWith(queryLower)) score = 80;
    else if (fileName.includes(queryLower)) score = 50;
    else if (filePath.toLowerCase().includes(queryLower)) score = 30;
    if (isDirectory && score > 0) score += 10;
    return score;
  }
};
var CompletionApplyStrategy = class {
  apply(lines, cursorLine, cursorCol, item, prefix) {
    const currentLine = lines[cursorLine] || "";
    const head = currentLine.slice(0, cursorCol - prefix.length);
    const tail = currentLine.slice(cursorCol);
    if (this.isCommandName(prefix, head)) {
      const rebuilt2 = `${head}/${item.value} ${tail}`;
      const caret2 = head.length + item.value.length + 2;
      return this.writeLine(lines, cursorLine, rebuilt2, caret2);
    }
    if (prefix.startsWith("@")) {
      const trailing = item.value.endsWith("/") ? "" : " ";
      const rebuilt2 = `${head}${item.value}${trailing}${tail}`;
      const caret2 = head.length + item.value.length + trailing.length;
      return this.writeLine(lines, cursorLine, rebuilt2, caret2);
    }
    const rebuilt = `${head}${item.value}${tail}`;
    const caret = head.length + item.value.length;
    return this.writeLine(lines, cursorLine, rebuilt, caret);
  }
  isCommandName(prefix, head) {
    return prefix.startsWith("/") && head.trim() === "" && !prefix.slice(1).includes("/");
  }
  writeLine(lines, cursorLine, line, cursorCol) {
    const newLines = [...lines];
    newLines[cursorLine] = line;
    return { lines: newLines, cursorLine, cursorCol };
  }
};
var CombinedAutocompleteProvider = class {
  constructor(commands = [], basePath = process.cwd(), fdPath = null) {
    this.basePath = basePath;
    this.fdPath = fdPath;
    this.commandEngine = new CommandAutocompleteEngine(commands);
    this.pathEngine = new PathAutocompleteEngine(this.basePath, this.fdPath);
  }
  basePath;
  fdPath;
  cache = new SuggestionCache();
  commandEngine;
  attachmentEngine = new AttachmentAutocompleteEngine();
  pathEngine;
  applyStrategy = new CompletionApplyStrategy();
  getSuggestions(lines, cursorLine, cursorCol) {
    const currentLine = lines[cursorLine] || "";
    const textBeforeCursor = currentLine.slice(0, cursorCol);
    const attachmentPrefix = this.attachmentEngine.getAttachmentPrefix(textBeforeCursor);
    if (attachmentPrefix) {
      const query = this.attachmentEngine.toQuery(attachmentPrefix);
      const items2 = this.cache.getOrLoad(`fuzzy:${query}`, () => this.pathEngine.getFuzzyFileSuggestions(query));
      if (items2.length > 0) return { items: items2, prefix: attachmentPrefix };
    }
    const commandSuggestions = this.commandEngine.getSuggestions(textBeforeCursor);
    if (commandSuggestions) return commandSuggestions;
    const pathPrefix = this.pathEngine.extractPathPrefix(textBeforeCursor, false);
    if (pathPrefix === null) return null;
    const items = this.cache.getOrLoad(`path:${pathPrefix}`, () => this.pathEngine.getFileSuggestions(pathPrefix));
    if (items.length === 0) return null;
    return { items, prefix: pathPrefix };
  }
  applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
    return this.applyStrategy.apply(lines, cursorLine, cursorCol, item, prefix);
  }
  clearCache() {
    this.cache.clear();
  }
  // Force file completion (called on Tab key) - always returns suggestions
  getForceFileSuggestions(lines, cursorLine, cursorCol) {
    const currentLine = lines[cursorLine] || "";
    const textBeforeCursor = currentLine.slice(0, cursorCol);
    if (!this.pathEngine.shouldTriggerForTab(textBeforeCursor)) return null;
    const pathPrefix = this.pathEngine.extractPathPrefix(textBeforeCursor, true);
    if (pathPrefix === null) return null;
    const items = this.cache.getOrLoad(`path:${pathPrefix}`, () => this.pathEngine.getFileSuggestions(pathPrefix));
    if (items.length === 0) return null;
    return { items, prefix: pathPrefix };
  }
  // Check if we should trigger file completion (called on Tab key)
  shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
    const currentLine = lines[cursorLine] || "";
    const textBeforeCursor = currentLine.slice(0, cursorCol);
    return this.pathEngine.shouldTriggerForTab(textBeforeCursor);
  }
};

// src/ui/keys.ts
var _kittyProtocolActive = false;
function setKittyProtocolActive(active) {
  _kittyProtocolActive = active;
}
function isKittyProtocolActive() {
  return _kittyProtocolActive;
}
var SPECIAL_KEY_LITERAL_MAP = {
  escape: "escape",
  esc: "esc",
  enter: "enter",
  return: "return",
  tab: "tab",
  space: "space",
  backspace: "backspace",
  delete: "delete",
  insert: "insert",
  clear: "clear",
  home: "home",
  end: "end",
  pageUp: "pageUp",
  pageDown: "pageDown",
  up: "up",
  down: "down",
  left: "left",
  right: "right",
  f1: "f1",
  f2: "f2",
  f3: "f3",
  f4: "f4",
  f5: "f5",
  f6: "f6",
  f7: "f7",
  f8: "f8",
  f9: "f9",
  f10: "f10",
  f11: "f11",
  f12: "f12"
};
var SYMBOL_KEY_LITERAL_MAP = {
  backtick: "`",
  hyphen: "-",
  equals: "=",
  leftbracket: "[",
  rightbracket: "]",
  backslash: "\\",
  semicolon: ";",
  quote: "'",
  comma: ",",
  period: ".",
  slash: "/",
  exclamation: "!",
  at: "@",
  hash: "#",
  dollar: "$",
  percent: "%",
  caret: "^",
  ampersand: "&",
  asterisk: "*",
  leftparen: "(",
  rightparen: ")",
  underscore: "_",
  plus: "+",
  pipe: "|",
  tilde: "~",
  leftbrace: "{",
  rightbrace: "}",
  colon: ":",
  lessthan: "<",
  greaterthan: ">",
  question: "?"
};
var Key = {
  ...SPECIAL_KEY_LITERAL_MAP,
  ...SYMBOL_KEY_LITERAL_MAP,
  ctrl: (key) => `ctrl+${key}`,
  shift: (key) => `shift+${key}`,
  alt: (key) => `alt+${key}`,
  ctrlShift: (key) => `ctrl+shift+${key}`,
  shiftCtrl: (key) => `shift+ctrl+${key}`,
  ctrlAlt: (key) => `ctrl+alt+${key}`,
  altCtrl: (key) => `alt+ctrl+${key}`,
  shiftAlt: (key) => `shift+alt+${key}`,
  altShift: (key) => `alt+shift+${key}`,
  ctrlShiftAlt: (key) => `ctrl+shift+alt+${key}`
};
var SYMBOL_KEYS = /* @__PURE__ */ new Set([
  "`",
  "-",
  "=",
  "[",
  "]",
  "\\",
  ";",
  "'",
  ",",
  ".",
  "/",
  "!",
  "@",
  "#",
  "$",
  "%",
  "^",
  "&",
  "*",
  "(",
  ")",
  "_",
  "+",
  "|",
  "~",
  "{",
  "}",
  ":",
  "<",
  ">",
  "?"
]);
var MODIFIERS = {
  shift: 1,
  alt: 2,
  ctrl: 4,
  super: 8,
  hyper: 16,
  meta: 32,
  capsLock: 64,
  numLock: 128
};
var LOCK_MASK = MODIFIERS.capsLock | MODIFIERS.numLock;
var CODEPOINTS = {
  escape: 27,
  tab: 9,
  enter: 13,
  space: 32,
  backspace: 127,
  kpEnter: 57414
  // the keypad Enter key as reported under Kitty
};
var ARROW_CODEPOINTS = {
  up: -101,
  down: -102,
  right: -103,
  left: -104
};
var FUNCTIONAL_CODEPOINTS = {
  delete: -110,
  insert: -111,
  pageUp: -112,
  pageDown: -113,
  home: -114,
  end: -115
};
var LEGACY_KEY_SEQUENCES = {
  arrowDown: ["\x1B[B", "\x1BOB"],
  arrowLeft: ["\x1B[D", "\x1BOD"],
  arrowRight: ["\x1B[C", "\x1BOC"],
  arrowUp: ["\x1B[A", "\x1BOA"],
  clear: ["\x1B[E", "\x1BOE"],
  delete: ["\x1B[3~"],
  end: ["\x1B[F", "\x1BOF", "\x1B[4~", "\x1B[8~"],
  f1: ["\x1BOP", "\x1B[11~", "\x1B[[A"],
  f10: ["\x1B[21~"],
  f11: ["\x1B[23~"],
  f12: ["\x1B[24~"],
  f2: ["\x1BOQ", "\x1B[12~", "\x1B[[B"],
  f3: ["\x1BOR", "\x1B[13~", "\x1B[[C"],
  f4: ["\x1BOS", "\x1B[14~", "\x1B[[D"],
  f5: ["\x1B[15~", "\x1B[[E"],
  f6: ["\x1B[17~"],
  f7: ["\x1B[18~"],
  f8: ["\x1B[19~"],
  f9: ["\x1B[20~"],
  home: ["\x1B[H", "\x1BOH", "\x1B[1~", "\x1B[7~"],
  insert: ["\x1B[2~"],
  pageDown: ["\x1B[6~", "\x1B[[6~"],
  pageUp: ["\x1B[5~", "\x1B[[5~"]
};
var LEGACY_SHIFT_SEQUENCES = {
  arrowDown: ["\x1B[b"],
  arrowLeft: ["\x1B[d"],
  arrowRight: ["\x1B[c"],
  arrowUp: ["\x1B[a"],
  clear: ["\x1B[e"],
  delete: ["\x1B[3$"],
  end: ["\x1B[8$"],
  home: ["\x1B[7$"],
  insert: ["\x1B[2$"],
  pageDown: ["\x1B[6$"],
  pageUp: ["\x1B[5$"]
};
var LEGACY_CTRL_SEQUENCES = {
  arrowDown: ["\x1BOb"],
  arrowLeft: ["\x1BOd"],
  arrowRight: ["\x1BOc"],
  arrowUp: ["\x1BOa"],
  clear: ["\x1BOe"],
  delete: ["\x1B[3^"],
  end: ["\x1B[8^"],
  home: ["\x1B[7^"],
  insert: ["\x1B[2^"],
  pageDown: ["\x1B[6^"],
  pageUp: ["\x1B[5^"]
};
var LEGACY_SEQUENCE_KEY_IDS = {
  "\x1BOA": "up",
  "\x1BOB": "down",
  "\x1BOC": "right",
  "\x1BOD": "left",
  "\x1BOH": "home",
  "\x1BOF": "end",
  "\x1B[E": "clear",
  "\x1BOE": "clear",
  "\x1BOe": "ctrl+clear",
  "\x1B[e": "shift+clear",
  "\x1B[2~": "insert",
  "\x1B[2$": "shift+insert",
  "\x1B[2^": "ctrl+insert",
  "\x1B[3$": "shift+delete",
  "\x1B[3^": "ctrl+delete",
  "\x1B[[5~": "pageUp",
  "\x1B[[6~": "pageDown",
  "\x1B[a": "shift+up",
  "\x1B[b": "shift+down",
  "\x1B[c": "shift+right",
  "\x1B[d": "shift+left",
  "\x1BOa": "ctrl+up",
  "\x1BOb": "ctrl+down",
  "\x1BOc": "ctrl+right",
  "\x1BOd": "ctrl+left",
  "\x1B[5$": "shift+pageUp",
  "\x1B[6$": "shift+pageDown",
  "\x1B[7$": "shift+home",
  "\x1B[8$": "shift+end",
  "\x1B[5^": "ctrl+pageUp",
  "\x1B[6^": "ctrl+pageDown",
  "\x1B[7^": "ctrl+home",
  "\x1B[8^": "ctrl+end",
  "\x1BOP": "f1",
  "\x1BOQ": "f2",
  "\x1BOR": "f3",
  "\x1BOS": "f4",
  "\x1B[11~": "f1",
  "\x1B[12~": "f2",
  "\x1B[13~": "f3",
  "\x1B[14~": "f4",
  "\x1B[[A": "f1",
  "\x1B[[B": "f2",
  "\x1B[[C": "f3",
  "\x1B[[D": "f4",
  "\x1B[[E": "f5",
  "\x1B[15~": "f5",
  "\x1B[17~": "f6",
  "\x1B[18~": "f7",
  "\x1B[19~": "f8",
  "\x1B[20~": "f9",
  "\x1B[21~": "f10",
  "\x1B[23~": "f11",
  "\x1B[24~": "f12",
  "\x1Bb": "alt+left",
  "\x1Bf": "alt+right",
  "\x1Bp": "alt+up",
  "\x1Bn": "alt+down"
};
var matchesLegacySequence = (data, sequences) => sequences.includes(data);
var matchesLegacyModifierSequence = (data, key, modifier) => {
  if (modifier === MODIFIERS.shift) {
    return matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);
  }
  if (modifier === MODIFIERS.ctrl) {
    return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
  }
  return false;
};
var _lastEventType = "press";
function isKeyRelease(data) {
  if (data.includes("\x1B[200~")) {
    return false;
  }
  if (data.includes(":3u") || data.includes(":3~") || data.includes(":3A") || data.includes(":3B") || data.includes(":3C") || data.includes(":3D") || data.includes(":3H") || data.includes(":3F")) {
    return true;
  }
  return false;
}
function isKeyRepeat(data) {
  if (data.includes("\x1B[200~")) {
    return false;
  }
  if (data.includes(":2u") || data.includes(":2~") || data.includes(":2A") || data.includes(":2B") || data.includes(":2C") || data.includes(":2D") || data.includes(":2H") || data.includes(":2F")) {
    return true;
  }
  return false;
}
var EVENT_TYPE_BY_CODE = {
  2: "repeat",
  3: "release"
};
function parseEventType(eventTypeStr) {
  if (!eventTypeStr) {
    return "press";
  }
  const code = Number.parseInt(eventTypeStr, 10);
  return EVENT_TYPE_BY_CODE[code] ?? "press";
}
function scanDigits(text, from) {
  let cursor = from;
  while (cursor < text.length) {
    const ch = text.charCodeAt(cursor);
    if (ch < 48 || ch > 57) {
      break;
    }
    cursor += 1;
  }
  return { text: text.slice(from, cursor), next: cursor };
}
function takeTrailer(text, cursor, separator, requireDigit) {
  if (text[cursor] !== separator) {
    return { value: null, next: cursor };
  }
  const afterSep = cursor + 1;
  const digits = scanDigits(text, afterSep);
  if (requireDigit && digits.text.length === 0) {
    return { value: null, next: cursor };
  }
  return { value: digits.text, next: digits.next };
}
function decodeCsiUBody(body) {
  const codepoint = scanDigits(body, 0);
  if (codepoint.text.length === 0) {
    return null;
  }
  let cursor = codepoint.next;
  let shifted = null;
  if (body[cursor] === ":") {
    const afterColon = cursor + 1;
    const digits = scanDigits(body, afterColon);
    shifted = digits.text;
    cursor = digits.next;
  }
  const baseTrailer = takeTrailer(body, cursor, ":", true);
  cursor = baseTrailer.next;
  const modTrailer = takeTrailer(body, cursor, ";", true);
  cursor = modTrailer.next;
  const eventTrailer = takeTrailer(body, cursor, ":", true);
  cursor = eventTrailer.next;
  if (cursor !== body.length) {
    return null;
  }
  return {
    codepoint: codepoint.text,
    shifted,
    base: baseTrailer.value,
    mod: modTrailer.value,
    event: eventTrailer.value
  };
}
function modifierMaskFromField(field) {
  const reported = field === null ? 1 : Number.parseInt(field, 10);
  return reported - 1;
}
var NAV_LETTER_CODEPOINTS = {
  A: ARROW_CODEPOINTS.up,
  B: ARROW_CODEPOINTS.down,
  C: ARROW_CODEPOINTS.right,
  D: ARROW_CODEPOINTS.left,
  H: FUNCTIONAL_CODEPOINTS.home,
  F: FUNCTIONAL_CODEPOINTS.end
};
var TILDE_NUMBER_CODEPOINTS = {
  2: FUNCTIONAL_CODEPOINTS.insert,
  3: FUNCTIONAL_CODEPOINTS.delete,
  5: FUNCTIONAL_CODEPOINTS.pageUp,
  6: FUNCTIONAL_CODEPOINTS.pageDown,
  7: FUNCTIONAL_CODEPOINTS.home,
  8: FUNCTIONAL_CODEPOINTS.end
};
function decodeNavLetterSequence(data) {
  const navMatch = /^\x1b\[1;(\d+)(?::(\d+))?([ABCDHF])$/.exec(data);
  if (!navMatch) {
    return null;
  }
  const codepoint = NAV_LETTER_CODEPOINTS[navMatch[3]];
  const eventType = parseEventType(navMatch[2]);
  _lastEventType = eventType;
  return { codepoint, modifier: modifierMaskFromField(navMatch[1]), eventType };
}
function decodeTildeSequence(data) {
  const tildeMatch = /^\x1b\[(\d+)(?:;(\d+))?(?::(\d+))?~$/.exec(data);
  if (!tildeMatch) {
    return null;
  }
  const codepoint = TILDE_NUMBER_CODEPOINTS[Number.parseInt(tildeMatch[1], 10)];
  if (codepoint === void 0) {
    return null;
  }
  const eventType = parseEventType(tildeMatch[3]);
  _lastEventType = eventType;
  return { codepoint, modifier: modifierMaskFromField(tildeMatch[2] ?? null), eventType };
}
function decodeCsiUSequence(data) {
  if (!data.startsWith("\x1B[") || !data.endsWith("u")) {
    return null;
  }
  const fields = decodeCsiUBody(data.slice(2, -1));
  if (!fields) {
    return null;
  }
  const codepoint = Number.parseInt(fields.codepoint, 10);
  const shiftedKey = fields.shifted && fields.shifted.length > 0 ? Number.parseInt(fields.shifted, 10) : void 0;
  const baseLayoutKey = fields.base !== null ? Number.parseInt(fields.base, 10) : void 0;
  const eventType = parseEventType(fields.event ?? void 0);
  _lastEventType = eventType;
  return {
    codepoint,
    shiftedKey,
    baseLayoutKey,
    modifier: modifierMaskFromField(fields.mod),
    eventType
  };
}
function parseKittySequence(data) {
  return decodeCsiUSequence(data) ?? decodeNavLetterSequence(data) ?? decodeTildeSequence(data);
}
function matchesKittySequence(data, expectedCodepoint, expectedModifier) {
  const parsed = parseKittySequence(data);
  if (!parsed) return false;
  const actualMod = parsed.modifier & ~LOCK_MASK;
  const expectedMod = expectedModifier & ~LOCK_MASK;
  if (actualMod !== expectedMod) return false;
  if (parsed.codepoint === expectedCodepoint) return true;
  if (parsed.baseLayoutKey !== void 0 && parsed.baseLayoutKey === expectedCodepoint) return true;
  return false;
}
function matchesModifyOtherKeys(data, expectedKeycode, expectedModifier) {
  const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
  if (!match) return false;
  const modValue = parseInt(match[1], 10);
  const keycode = parseInt(match[2], 10);
  const actualMod = modValue - 1;
  return keycode === expectedKeycode && actualMod === expectedModifier;
}
function rawCtrlChar(key) {
  const char = key.toLowerCase();
  const code = char.charCodeAt(0);
  if (code >= 97 && code <= 122 || char === "[" || char === "\\" || char === "]" || char === "_") {
    return String.fromCharCode(code & 31);
  }
  if (char === "-") {
    return String.fromCharCode(31);
  }
  return null;
}
var SPECIAL_KEY_ALIASES = {
  esc: "escape",
  return: "enter",
  pageup: "pageUp",
  pagedown: "pageDown"
};
var SPECIAL_KEY_SET = /* @__PURE__ */ new Set([
  "escape",
  "enter",
  "tab",
  "space",
  "backspace",
  "delete",
  "insert",
  "clear",
  "home",
  "end",
  "pageUp",
  "pageDown",
  "up",
  "down",
  "left",
  "right",
  "f1",
  "f2",
  "f3",
  "f4",
  "f5",
  "f6",
  "f7",
  "f8",
  "f9",
  "f10",
  "f11",
  "f12"
]);
function normalizeKeyId(keyId) {
  const parts = keyId.split("+").map((p) => p.trim()).filter(Boolean);
  if (parts.length === 0) return "";
  const key = parts[parts.length - 1].toLowerCase();
  const normalizedKey = SPECIAL_KEY_ALIASES[key] ?? key;
  const modifierSet = new Set(parts.slice(0, -1).map((p) => p.toLowerCase()));
  const modifiers = [];
  if (modifierSet.has("ctrl")) modifiers.push("ctrl");
  if (modifierSet.has("shift")) modifiers.push("shift");
  if (modifierSet.has("alt")) modifiers.push("alt");
  return modifiers.length > 0 ? `${modifiers.join("+")}+${normalizedKey}` : normalizedKey;
}
var KeyValidator = class {
  normalize(keyId) {
    return normalizeKeyId(keyId);
  }
  isValid(keyId) {
    const normalized = normalizeKeyId(keyId);
    if (!normalized) return false;
    const parts = normalized.split("+");
    const key = parts[parts.length - 1];
    if (key.length === 1) return key >= "a" && key <= "z" || SYMBOL_KEYS.has(key);
    return SPECIAL_KEY_SET.has(key);
  }
};
var keyValidator = new KeyValidator();
function parseKeyId(keyId) {
  const normalized = normalizeKeyId(keyId);
  if (!normalized || !keyValidator.isValid(normalized)) return null;
  const parts = normalized.split("+");
  const key = parts[parts.length - 1];
  if (!key) return null;
  return {
    key,
    ctrl: parts.includes("ctrl"),
    shift: parts.includes("shift"),
    alt: parts.includes("alt")
  };
}
var KeyMatchEngine = class {
  specialKeyResolverRegistry;
  constructor() {
    this.specialKeyResolverRegistry = {
      escape: (data, _decodedKeyId, modifierBits) => {
        if (modifierBits !== 0) return false;
        return data === "\x1B" || matchesKittySequence(data, CODEPOINTS.escape, 0);
      },
      esc: (data, _decodedKeyId, modifierBits) => {
        if (modifierBits !== 0) return false;
        return data === "\x1B" || matchesKittySequence(data, CODEPOINTS.escape, 0);
      },
      space: (data, decodedKeyId, modifierBits) => {
        const { ctrl, shift, alt } = decodedKeyId;
        if (!_kittyProtocolActive) {
          if (ctrl && !alt && !shift && data === "\0") return true;
          if (alt && !ctrl && !shift && data === "\x1B ") return true;
        }
        if (modifierBits === 0) return data === " " || matchesKittySequence(data, CODEPOINTS.space, 0);
        return matchesKittySequence(data, CODEPOINTS.space, modifierBits);
      },
      tab: (data, decodedKeyId, modifierBits) => {
        const { ctrl, shift, alt } = decodedKeyId;
        if (shift && !ctrl && !alt) {
          return data === "\x1B[Z" || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift);
        }
        if (modifierBits === 0) return data === "	" || matchesKittySequence(data, CODEPOINTS.tab, 0);
        return matchesKittySequence(data, CODEPOINTS.tab, modifierBits);
      },
      enter: (data, decodedKeyId, modifierBits) => this.matchEnterLikeKey(data, decodedKeyId, modifierBits),
      return: (data, decodedKeyId, modifierBits) => this.matchEnterLikeKey(data, decodedKeyId, modifierBits),
      backspace: (data, decodedKeyId, modifierBits) => {
        const { ctrl, shift, alt } = decodedKeyId;
        if (alt && !ctrl && !shift) {
          if (data === "\x1B\x7F" || data === "\x1B\b") return true;
          return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt);
        }
        if (modifierBits === 0) {
          return data === "\x7F" || data === "\b" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
        }
        return matchesKittySequence(data, CODEPOINTS.backspace, modifierBits);
      },
      insert: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "insert", FUNCTIONAL_CODEPOINTS.insert, modifierBits),
      delete: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "delete", FUNCTIONAL_CODEPOINTS.delete, modifierBits),
      clear: (data, _decodedKeyId, modifierBits) => {
        if (modifierBits === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);
        return matchesLegacyModifierSequence(data, "clear", modifierBits);
      },
      home: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "home", FUNCTIONAL_CODEPOINTS.home, modifierBits),
      end: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "end", FUNCTIONAL_CODEPOINTS.end, modifierBits),
      pageup: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "pageUp", FUNCTIONAL_CODEPOINTS.pageUp, modifierBits),
      pagedown: (data, _decodedKeyId, modifierBits) => this.matchFunctionalKey(data, "pageDown", FUNCTIONAL_CODEPOINTS.pageDown, modifierBits),
      up: (data, decodedKeyId, modifierBits) => this.matchArrowKey(data, "arrowUp", ARROW_CODEPOINTS.up, decodedKeyId, modifierBits),
      down: (data, decodedKeyId, modifierBits) => this.matchArrowKey(data, "arrowDown", ARROW_CODEPOINTS.down, decodedKeyId, modifierBits),
      left: (data, decodedKeyId, modifierBits) => this.matchLeftArrow(data, decodedKeyId, modifierBits),
      right: (data, decodedKeyId, modifierBits) => this.matchRightArrow(data, decodedKeyId, modifierBits),
      f1: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f1", modifierBits),
      f2: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f2", modifierBits),
      f3: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f3", modifierBits),
      f4: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f4", modifierBits),
      f5: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f5", modifierBits),
      f6: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f6", modifierBits),
      f7: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f7", modifierBits),
      f8: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f8", modifierBits),
      f9: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f9", modifierBits),
      f10: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f10", modifierBits),
      f11: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f11", modifierBits),
      f12: (data, _decodedKeyId, modifierBits) => this.matchFunctionKey(data, "f12", modifierBits)
    };
  }
  matches(data, keyId) {
    const decodedKeyId = parseKeyId(keyId);
    if (!decodedKeyId) return false;
    const modifierBits = this.computeModifierMask(decodedKeyId);
    const specialKeyResolver = this.specialKeyResolverRegistry[decodedKeyId.key];
    if (specialKeyResolver) {
      return specialKeyResolver(data, decodedKeyId, modifierBits);
    }
    return this.matchPrintableKey(data, decodedKeyId, modifierBits);
  }
  computeModifierMask(decodedKeyId) {
    let modifierBits = 0;
    if (decodedKeyId.shift) modifierBits |= MODIFIERS.shift;
    if (decodedKeyId.alt) modifierBits |= MODIFIERS.alt;
    if (decodedKeyId.ctrl) modifierBits |= MODIFIERS.ctrl;
    return modifierBits;
  }
  matchFunctionKey(data, functionKey, modifierBits) {
    if (modifierBits !== 0) return false;
    return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);
  }
  matchEnterLikeKey(data, decodedKeyId, modifierBits) {
    const { ctrl, shift, alt } = decodedKeyId;
    if (shift && !ctrl && !alt) {
      if (matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) || matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.shift)) {
        return true;
      }
      if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.shift)) {
        return true;
      }
      if (_kittyProtocolActive) {
        return data === "\x1B\r" || data === "\n";
      }
      return false;
    }
    if (alt && !ctrl && !shift) {
      if (matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) || matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.alt)) {
        return true;
      }
      if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.alt)) {
        return true;
      }
      if (!_kittyProtocolActive) {
        return data === "\x1B\r";
      }
      return false;
    }
    if (modifierBits === 0) {
      return data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM" || matchesKittySequence(data, CODEPOINTS.enter, 0) || matchesKittySequence(data, CODEPOINTS.kpEnter, 0);
    }
    return matchesKittySequence(data, CODEPOINTS.enter, modifierBits) || matchesKittySequence(data, CODEPOINTS.kpEnter, modifierBits);
  }
  matchFunctionalKey(data, legacyName, codepoint, modifierBits) {
    if (modifierBits === 0) {
      return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[legacyName]) || matchesKittySequence(data, codepoint, 0);
    }
    if (matchesLegacyModifierSequence(data, legacyName, modifierBits)) {
      return true;
    }
    return matchesKittySequence(data, codepoint, modifierBits);
  }
  matchArrowKey(data, arrowName, codepoint, decodedKeyId, modifierBits) {
    const { ctrl, shift, alt } = decodedKeyId;
    if (alt && !ctrl && !shift) {
      const altLegacy = arrowName === "arrowUp" ? "\x1Bp" : "\x1Bn";
      return data === altLegacy || matchesKittySequence(data, codepoint, MODIFIERS.alt);
    }
    if (modifierBits === 0) {
      return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[arrowName]) || matchesKittySequence(data, codepoint, 0);
    }
    if (matchesLegacyModifierSequence(data, arrowName, modifierBits)) {
      return true;
    }
    return matchesKittySequence(data, codepoint, modifierBits);
  }
  matchLeftArrow(data, decodedKeyId, modifierBits) {
    const { ctrl, shift, alt } = decodedKeyId;
    if (alt && !ctrl && !shift) {
      return data === "\x1B[1;3D" || !_kittyProtocolActive && data === "\x1BB" || data === "\x1Bb" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);
    }
    if (ctrl && !alt && !shift) {
      return data === "\x1B[1;5D" || matchesLegacyModifierSequence(data, "arrowLeft", MODIFIERS.ctrl) || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);
    }
    if (modifierBits === 0) {
      return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.arrowLeft) || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
    }
    if (matchesLegacyModifierSequence(data, "arrowLeft", modifierBits)) {
      return true;
    }
    return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifierBits);
  }
  matchRightArrow(data, decodedKeyId, modifierBits) {
    const { ctrl, shift, alt } = decodedKeyId;
    if (alt && !ctrl && !shift) {
      return data === "\x1B[1;3C" || !_kittyProtocolActive && data === "\x1BF" || data === "\x1Bf" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);
    }
    if (ctrl && !alt && !shift) {
      return data === "\x1B[1;5C" || matchesLegacyModifierSequence(data, "arrowRight", MODIFIERS.ctrl) || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);
    }
    if (modifierBits === 0) {
      return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.arrowRight) || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
    }
    if (matchesLegacyModifierSequence(data, "arrowRight", modifierBits)) {
      return true;
    }
    return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifierBits);
  }
  matchPrintableKey(data, decodedKeyId, modifierBits) {
    const { key, ctrl, shift, alt } = decodedKeyId;
    if (!(key.length === 1 && (key >= "a" && key <= "z" || SYMBOL_KEYS.has(key)))) {
      return false;
    }
    const codepoint = key.charCodeAt(0);
    const controlCharacter = rawCtrlChar(key);
    if (ctrl && alt && !shift && !_kittyProtocolActive && controlCharacter) {
      return data === `\x1B${controlCharacter}`;
    }
    if (alt && !ctrl && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
      if (data === `\x1B${key}`) {
        return true;
      }
    }
    if (ctrl && !shift && !alt) {
      if (controlCharacter && data === controlCharacter) {
        return true;
      }
      return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
    }
    if (ctrl && shift && !alt) {
      return matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl);
    }
    if (shift && !ctrl && !alt) {
      if (data === key.toUpperCase()) {
        return true;
      }
      return matchesKittySequence(data, codepoint, MODIFIERS.shift);
    }
    if (modifierBits !== 0) {
      return matchesKittySequence(data, codepoint, modifierBits);
    }
    return data === key || matchesKittySequence(data, codepoint, 0);
  }
};
var KeyParseEngine = class {
  legacyLiteralParseTable = {
    "\x1B": "escape",
    "": "ctrl+\\",
    "": "ctrl+]",
    "": "ctrl+-",
    "\x1B\x1B": "ctrl+alt+[",
    "\x1B": "ctrl+alt+\\",
    "\x1B": "ctrl+alt+]",
    "\x1B": "ctrl+alt+-",
    "	": "tab",
    "\0": "ctrl+space",
    " ": "space",
    "\x1B[Z": "shift+tab",
    "\x1B[A": "up",
    "\x1B[B": "down",
    "\x1B[C": "right",
    "\x1B[D": "left",
    "\x1B[3~": "delete",
    "\x1B[5~": "pageUp",
    "\x1B[6~": "pageDown"
  };
  parse(data) {
    const kittyDerivedKeyId = this.parseFromKitty(data);
    if (kittyDerivedKeyId) {
      return kittyDerivedKeyId;
    }
    return this.parseFromLegacy(data);
  }
  parseFromKitty(data) {
    const parsedKitty = parseKittySequence(data);
    if (!parsedKitty) {
      return void 0;
    }
    const orderedModifiers = this.formatModifierNames(parsedKitty.modifier & ~LOCK_MASK);
    const lookupCodepoint = parsedKitty.baseLayoutKey ?? parsedKitty.codepoint;
    const keyName = this.resolveKeyName(lookupCodepoint);
    if (!keyName) return void 0;
    return orderedModifiers.length > 0 ? `${orderedModifiers.join("+")}+${keyName}` : keyName;
  }
  parseFromLegacy(data) {
    if (_kittyProtocolActive && (data === "\x1B\r" || data === "\n")) {
      return "shift+enter";
    }
    const mappedLegacyIdentifier = LEGACY_SEQUENCE_KEY_IDS[data];
    if (mappedLegacyIdentifier) {
      return mappedLegacyIdentifier;
    }
    if (data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM") {
      return "enter";
    }
    if (data === "\x7F" || data === "\b") {
      return "backspace";
    }
    if (!_kittyProtocolActive && data === "\x1B\r") {
      return "alt+enter";
    }
    if (!_kittyProtocolActive && data === "\x1B ") {
      return "alt+space";
    }
    if (data === "\x1B\x7F" || data === "\x1B\b") {
      return "alt+backspace";
    }
    if (!_kittyProtocolActive && data === "\x1BB") {
      return "alt+left";
    }
    if (!_kittyProtocolActive && data === "\x1BF") {
      return "alt+right";
    }
    if (data === "\x1B[H" || data === "\x1BOH") {
      return "home";
    }
    if (data === "\x1B[F" || data === "\x1BOF") {
      return "end";
    }
    const exactMatch = this.legacyLiteralParseTable[data];
    if (exactMatch) {
      return exactMatch;
    }
    if (!_kittyProtocolActive && data.length === 2 && data[0] === "\x1B") {
      const legacyCode = data.charCodeAt(1);
      if (legacyCode >= 1 && legacyCode <= 26) {
        return `ctrl+alt+${String.fromCharCode(legacyCode + 96)}`;
      }
      if (legacyCode >= 97 && legacyCode <= 122) {
        return `alt+${String.fromCharCode(legacyCode)}`;
      }
    }
    if (data.length === 1) {
      const rawCode = data.charCodeAt(0);
      if (rawCode >= 1 && rawCode <= 26) {
        return `ctrl+${String.fromCharCode(rawCode + 96)}`;
      }
      if (rawCode >= 32 && rawCode <= 126) {
        return data;
      }
    }
    return void 0;
  }
  resolveKeyName(codepoint) {
    if (codepoint === CODEPOINTS.escape) return "escape";
    if (codepoint === CODEPOINTS.tab) return "tab";
    if (codepoint === CODEPOINTS.enter || codepoint === CODEPOINTS.kpEnter) return "enter";
    if (codepoint === CODEPOINTS.space) return "space";
    if (codepoint === CODEPOINTS.backspace) return "backspace";
    if (codepoint === FUNCTIONAL_CODEPOINTS.delete) return "delete";
    if (codepoint === FUNCTIONAL_CODEPOINTS.insert) return "insert";
    if (codepoint === FUNCTIONAL_CODEPOINTS.home) return "home";
    if (codepoint === FUNCTIONAL_CODEPOINTS.end) return "end";
    if (codepoint === FUNCTIONAL_CODEPOINTS.pageUp) return "pageUp";
    if (codepoint === FUNCTIONAL_CODEPOINTS.pageDown) return "pageDown";
    if (codepoint === ARROW_CODEPOINTS.up) return "up";
    if (codepoint === ARROW_CODEPOINTS.down) return "down";
    if (codepoint === ARROW_CODEPOINTS.left) return "left";
    if (codepoint === ARROW_CODEPOINTS.right) return "right";
    if (codepoint >= 97 && codepoint <= 122) return String.fromCharCode(codepoint);
    const char = String.fromCharCode(codepoint);
    if (SYMBOL_KEYS.has(char)) {
      return char;
    }
    return void 0;
  }
  formatModifierNames(modifierBits) {
    const orderedModifiers = [];
    if (modifierBits & MODIFIERS.shift) orderedModifiers.push("shift");
    if (modifierBits & MODIFIERS.ctrl) orderedModifiers.push("ctrl");
    if (modifierBits & MODIFIERS.alt) orderedModifiers.push("alt");
    return orderedModifiers;
  }
};
var keyMatcherEngine = new KeyMatchEngine();
var keyParserEngine = new KeyParseEngine();
function matchesKey(data, keyId) {
  return keyMatcherEngine.matches(data, keyId);
}
function parseKey(data) {
  return keyParserEngine.parse(data);
}

// src/ui/keybindings.ts
function flattenBindingBlocks(...blocks) {
  const assembled = {};
  for (const block of blocks) {
    for (const action of Object.keys(block)) {
      assembled[action] = block[action];
    }
  }
  return assembled;
}
var DEFAULT_EDITOR_KEYBINDINGS = flattenBindingBlocks(
  // caret motion
  // [readline]   ctrl+a   -> beginning-of-line  (cursorLineStart)
  // [readline]   ctrl+e   -> end-of-line        (cursorLineEnd)
  // [readline]   alt+b/f  -> backward/forward-word (cursorWordLeft/Right;
  //              we accept alt+arrows and ctrl+arrows as the visible chord)
  // [indus-original]  arrow keys, home/end, pageUp/pageDown -- terminal
  //                   conventions readline does not standardize on its own
  {
    cursorUp: "up",
    cursorDown: "down",
    cursorLeft: "left",
    cursorRight: "right",
    cursorWordLeft: ["alt+left", "ctrl+left"],
    cursorWordRight: ["alt+right", "ctrl+right"],
    cursorLineStart: ["home", "ctrl+a"],
    cursorLineEnd: ["end", "ctrl+e"],
    pageUp: "pageUp",
    pageDown: "pageDown"
  },
  // deletion
  // [readline]   ctrl+w   -> unix-word-rubout    (deleteWordBackward)
  // [readline]   alt+d    -> kill-word           (deleteWordForward)
  // [readline]   ctrl+u   -> unix-line-discard   (deleteToLineStart)
  // [readline]   ctrl+k   -> kill-line           (deleteToLineEnd)
  // [indus-original]  backspace/delete + alt+backspace/alt+delete
  {
    deleteCharBackward: "backspace",
    deleteCharForward: "delete",
    deleteWordBackward: ["ctrl+w", "alt+backspace"],
    deleteWordForward: ["alt+d", "alt+delete"],
    deleteToLineStart: "ctrl+u",
    deleteToLineEnd: "ctrl+k"
  },
  // text entry
  // [readline]   enter    -> accept-line         (submit)
  // [indus-original]  shift+enter inserts a literal newline; tab is reserved
  //                   for completion (readline binds tab to complete itself)
  {
    newLine: "shift+enter",
    submit: "enter",
    tab: "tab"
  },
  // completion popup -- all [indus-original]; readline's completion is a
  // single-shot menu, not a navigable popup, so it has no analogue.
  {
    selectUp: "up",
    selectDown: "down",
    selectPageUp: "pageUp",
    selectPageDown: "pageDown",
    selectConfirm: "enter",
    selectCancel: ["escape", "ctrl+c"]
  },
  // clipboard + kill ring
  // [readline]   ctrl+y   -> yank                (yank)
  // [readline]   alt+y    -> yank-pop            (yankPop)
  // [indus-original]  ctrl+c -> copy selection to the OS clipboard
  {
    copy: "ctrl+c",
    yank: "ctrl+y",
    yankPop: "alt+y"
  },
  // history
  // [indus-original]  ctrl+- -> undo (readline binds ctrl+_ to undo; we
  //                   accept the more terminal-portable ctrl+- chord)
  {
    undo: "ctrl+-"
  },
  // tool expansion -- [indus-original], no readline analogue
  {
    expandTools: "ctrl+o"
  },
  // session management -- all [indus-original], no readline analogue
  {
    toggleSessionPath: "ctrl+p",
    toggleSessionSort: "ctrl+s",
    renameSession: "ctrl+r",
    deleteSession: "ctrl+d",
    deleteSessionNoninvasive: "ctrl+backspace"
  }
);
var VIM_EDITOR_KEYBINDINGS = flattenBindingBlocks({
  cursorUp: ["up", "k"],
  cursorDown: ["down", "j"],
  cursorLeft: ["left", "h"],
  cursorRight: ["right", "l"],
  deleteCharBackward: ["backspace", "x"]
});
function toKeyIdArray(keys) {
  const source = Array.isArray(keys) ? keys : [keys];
  return source.map((key) => normalizeKeyId(key));
}
var KeybindingRegistry = class {
  actionToKeys = /* @__PURE__ */ new Map();
  registeredActions = /* @__PURE__ */ new Set();
  reset() {
    this.actionToKeys.clear();
    this.registeredActions.clear();
  }
  setAction(action, keys) {
    this.actionToKeys.set(action, toKeyIdArray(keys));
    this.registeredActions.add(action);
  }
  getActionKeys(action) {
    return this.actionToKeys.get(action) ?? [];
  }
  entries() {
    return this.actionToKeys.entries();
  }
  actions() {
    return [...this.registeredActions];
  }
};
var PresetResolver = class {
  static layersFor(preset) {
    const layers = [DEFAULT_EDITOR_KEYBINDINGS];
    if (preset === "vim") layers.push(VIM_EDITOR_KEYBINDINGS);
    return layers;
  }
  static mergeInto(registry, preset, config) {
    for (const layer of this.layersFor(preset)) {
      this.applyLayer(registry, layer);
    }
    this.applyLayer(registry, config);
  }
  static applyLayer(registry, layer) {
    for (const [action, keys] of Object.entries(layer)) {
      if (keys === void 0) continue;
      registry.setAction(action, keys);
    }
  }
};
var ConflictScanner = class {
  static scan(entries) {
    const keyToActions = /* @__PURE__ */ new Map();
    for (const [action, keys] of entries) {
      for (const key of keys) {
        const normalized = normalizeKeyId(key);
        if (!keyToActions.has(normalized)) keyToActions.set(normalized, /* @__PURE__ */ new Set());
        keyToActions.get(normalized).add(action);
      }
    }
    const conflicts = [];
    for (const [key, actions] of keyToActions.entries()) {
      if (actions.size > 1) conflicts.push({ key, actions: [...actions] });
    }
    return conflicts;
  }
};
var EditorKeybindingsManager = class {
  registry = new KeybindingRegistry();
  componentOverrides = /* @__PURE__ */ new Map();
  constructor(config = {}, preset = "default") {
    this.rebuildRegistry(config, preset);
  }
  rebuildRegistry(config, preset = "default") {
    this.registry.reset();
    PresetResolver.mergeInto(this.registry, preset, config);
  }
  resolveKeys(action, componentId) {
    if (componentId) {
      const overrideValue = this.componentOverrides.get(componentId)?.[action];
      if (overrideValue !== void 0) return toKeyIdArray(overrideValue);
    }
    return this.registry.getActionKeys(action);
  }
  /**
   * Report whether the given raw key data triggers the named action.
   */
  matches(data, action, componentId) {
    for (const key of this.resolveKeys(action, componentId)) {
      if (matchesKey(data, key)) return true;
    }
    return false;
  }
  /**
   * List every key currently wired to the given action.
   */
  getKeys(action) {
    return this.registry.getActionKeys(action);
  }
  registerAction(action, keys) {
    this.registry.setAction(action, keys);
  }
  getRegisteredActions() {
    return this.registry.actions();
  }
  /**
   * Swap in a fresh config (and optional preset), rebuilding the bindings.
   */
  setConfig(config, preset = "default") {
    this.rebuildRegistry(config, preset);
  }
  setComponentOverride(componentId, config) {
    this.componentOverrides.set(componentId, config);
  }
  clearComponentOverride(componentId) {
    this.componentOverrides.delete(componentId);
  }
  detectConflicts() {
    return ConflictScanner.scan(this.registry.entries());
  }
};
var globalEditorKeybindings = null;
function getEditorKeybindings() {
  if (!globalEditorKeybindings) {
    globalEditorKeybindings = new EditorKeybindingsManager();
  }
  return globalEditorKeybindings;
}
function setEditorKeybindings(manager) {
  globalEditorKeybindings = manager;
}

// src/ui/utils.ts
import { eastAsianWidth } from "get-east-asian-width";
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
var TextWidthCalculator = class {
  getWidth(text) {
    return visibleWidth(text);
  }
  clearCache() {
    widthCache.clear();
  }
};
var textWidthCalculator = new TextWidthCalculator();
function looksLikeEmojiCandidate(segment) {
  const cp = segment.codePointAt(0);
  return cp >= 126976 && cp <= 130047 || // pictographs and emoji proper
  cp >= 8960 && cp <= 9215 || // miscellaneous technical glyphs
  cp >= 9728 && cp <= 10175 || // assorted symbols and dingbats
  cp >= 11088 && cp <= 11093 || // a few star/circle characters
  segment.includes("\uFE0F") || // carries VS16, the emoji-presentation selector
  segment.length > 2;
}
function createUnicodeRegex(vPattern, uFallback) {
  try {
    return new RegExp(vPattern, "v");
  } catch {
    return new RegExp(uFallback, "u");
  }
}
var zeroWidthRegex = createUnicodeRegex(
  "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$",
  "^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Mark}|\\p{Surrogate})+$"
);
var leadingNonPrintingRegex = createUnicodeRegex(
  "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+",
  "^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+"
);
var rgiEmojiRegex = createUnicodeRegex("^\\p{RGI_Emoji}$", "^\\p{Extended_Pictographic}$");
var AnsiStripper = {
  strip(text) {
    if (!text.includes("\x1B")) {
      return text;
    }
    let cleaned = text;
    cleaned = cleaned.replace(/\x1b\[[0-9;]*[mGKHJ]/g, "");
    cleaned = cleaned.replace(/\x1b\]8;;[^\x07]*\x07/g, "");
    cleaned = cleaned.replace(/\x1b_[^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
    return cleaned;
  }
};
var WIDTH_CACHE_SIZE = 512;
var widthCache = /* @__PURE__ */ new Map();
function measureClusterWidth(segment) {
  if (zeroWidthRegex.test(segment)) {
    return 0;
  }
  if (looksLikeEmojiCandidate(segment) && rgiEmojiRegex.test(segment)) {
    return 2;
  }
  const base = segment.replace(leadingNonPrintingRegex, "");
  const cp = base.codePointAt(0);
  if (cp === void 0) {
    return 0;
  }
  let width = eastAsianWidth(cp);
  if (segment.length > 1) {
    for (const char of segment.slice(1)) {
      const c = char.codePointAt(0);
      if (c >= 65280 && c <= 65519) {
        width += eastAsianWidth(c);
      }
    }
  }
  return width;
}
function visibleWidth(str) {
  if (str.length === 0) {
    return 0;
  }
  let isPureAscii = true;
  for (let i = 0; i < str.length; i++) {
    const code = str.charCodeAt(i);
    if (code < 32 || code > 126) {
      isPureAscii = false;
      break;
    }
  }
  if (isPureAscii) {
    return str.length;
  }
  const cached = widthCache.get(str);
  if (cached !== void 0) {
    return cached;
  }
  let clean = str;
  if (str.includes("	")) {
    clean = clean.replace(/\t/g, "   ");
  }
  clean = AnsiStripper.strip(clean);
  let width = 0;
  for (const { segment } of segmenter.segment(clean)) {
    width += measureClusterWidth(segment);
  }
  if (widthCache.size >= WIDTH_CACHE_SIZE) {
    const firstKey = widthCache.keys().next().value;
    if (firstKey !== void 0) {
      widthCache.delete(firstKey);
    }
  }
  widthCache.set(str, width);
  return width;
}
function extractAnsiCode(str, pos) {
  if (pos >= str.length || str[pos] !== "\x1B") return null;
  const next = str[pos + 1];
  if (next === "[") {
    let j = pos + 2;
    while (j < str.length && !/[mGKHJ]/.test(str[j])) j++;
    if (j < str.length) return { code: str.substring(pos, j + 1), length: j + 1 - pos };
    return null;
  }
  if (next === "]") {
    let j = pos + 2;
    while (j < str.length) {
      if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
      if (str[j] === "\x1B" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
      j++;
    }
    return null;
  }
  if (next === "_") {
    let j = pos + 2;
    while (j < str.length) {
      if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
      if (str[j] === "\x1B" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
      j++;
    }
    return null;
  }
  return null;
}
var AnsiStateTracker = class {
  // Each attribute is kept on its own flag, which lets us clear them one at a time.
  _bold = false;
  _dim = false;
  italic = false;
  underline = false;
  blink = false;
  inverse = false;
  hidden = false;
  strikethrough = false;
  _colors = { fg: null, bg: null };
  process(ansiCode) {
    if (!ansiCode.endsWith("m")) {
      return;
    }
    const match = ansiCode.match(/\x1b\[([\d;]*)m/);
    if (!match) return;
    const params = match[1];
    if (params === "" || params === "0") {
      this.reset();
      return;
    }
    const parts = params.split(";");
    let i = 0;
    while (i < parts.length) {
      const code = Number.parseInt(parts[i] ?? "", 10);
      if (Number.isNaN(code)) {
        i++;
        continue;
      }
      const consumed = this.tryConsumeColorCode(parts, i, code);
      if (consumed > 0) {
        i += consumed;
        continue;
      }
      this.applyStandardCode(code);
      i++;
    }
  }
  tryConsumeColorCode(parts, index, code) {
    if (code !== 38 && code !== 48) {
      return 0;
    }
    if (parts[index + 1] === "5" && parts[index + 2] !== void 0) {
      const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]}`;
      if (code === 38) {
        this._colors.fg = colorCode;
      } else {
        this._colors.bg = colorCode;
      }
      return 3;
    }
    if (parts[index + 1] === "2" && parts[index + 4] !== void 0) {
      const colorCode = `${parts[index]};${parts[index + 1]};${parts[index + 2]};${parts[index + 3]};${parts[index + 4]}`;
      if (code === 38) {
        this._colors.fg = colorCode;
      } else {
        this._colors.bg = colorCode;
      }
      return 5;
    }
    return 0;
  }
  applyStandardCode(code) {
    switch (code) {
      case 0:
        this.reset();
        return;
      case 1:
        this._bold = true;
        return;
      case 2:
        this._dim = true;
        return;
      case 3:
        this.italic = true;
        return;
      case 4:
        this.underline = true;
        return;
      case 5:
        this.blink = true;
        return;
      case 7:
        this.inverse = true;
        return;
      case 8:
        this.hidden = true;
        return;
      case 9:
        this.strikethrough = true;
        return;
      case 21:
        this._bold = false;
        return;
      case 22:
        this._bold = false;
        this._dim = false;
        return;
      case 23:
        this.italic = false;
        return;
      case 24:
        this.underline = false;
        return;
      case 25:
        this.blink = false;
        return;
      case 27:
        this.inverse = false;
        return;
      case 28:
        this.hidden = false;
        return;
      case 29:
        this.strikethrough = false;
        return;
      case 39:
        this._colors.fg = null;
        return;
      case 49:
        this._colors.bg = null;
        return;
      default:
        if (code >= 30 && code <= 37 || code >= 90 && code <= 97) {
          this._colors.fg = String(code);
          return;
        }
        if (code >= 40 && code <= 47 || code >= 100 && code <= 107) {
          this._colors.bg = String(code);
        }
    }
  }
  reset() {
    this._bold = false;
    this._dim = false;
    this.italic = false;
    this.underline = false;
    this.blink = false;
    this.inverse = false;
    this.hidden = false;
    this.strikethrough = false;
    this._colors.fg = null;
    this._colors.bg = null;
  }
  /** Wipe all tracked state so the instance can be reused. */
  clear() {
    this.reset();
  }
  getActiveCodes() {
    const codes = [];
    if (this._bold) codes.push("1");
    if (this._dim) codes.push("2");
    if (this.italic) codes.push("3");
    if (this.underline) codes.push("4");
    if (this.blink) codes.push("5");
    if (this.inverse) codes.push("7");
    if (this.hidden) codes.push("8");
    if (this.strikethrough) codes.push("9");
    if (this._colors.fg) codes.push(this._colors.fg);
    if (this._colors.bg) codes.push(this._colors.bg);
    if (codes.length === 0) return "";
    return `\x1B[${codes.join(";")}m`;
  }
  hasActiveCodes() {
    return this._bold || this._dim || this.italic || this.underline || this.blink || this.inverse || this.hidden || this.strikethrough || this._colors.fg !== null || this._colors.bg !== null;
  }
  /**
   * Produce the escape code needed to switch off any attribute that would
   * otherwise smear into the padding at the end of a line. In practice that
   * is just underline. Yields an empty string when nothing needs disabling.
   */
  getLineEndReset() {
    if (this.underline) {
      return "\x1B[24m";
    }
    return "";
  }
};
function mergeTextIntoTracker(text, tracker) {
  let i = 0;
  while (i < text.length) {
    const ansiResult = extractAnsiCode(text, i);
    if (ansiResult) {
      tracker.process(ansiResult.code);
      i += ansiResult.length;
    } else {
      i++;
    }
  }
}
function tokenizeTextWithAnsi(text) {
  const tokens = [];
  let current = "";
  let pendingAnsi = "";
  let inWhitespace = false;
  let i = 0;
  while (i < text.length) {
    const ansiResult = extractAnsiCode(text, i);
    if (ansiResult) {
      pendingAnsi += ansiResult.code;
      i += ansiResult.length;
      continue;
    }
    const char = text[i];
    const charIsSpace = char === " ";
    if (charIsSpace !== inWhitespace && current) {
      tokens.push(current);
      current = "";
    }
    if (pendingAnsi) {
      current += pendingAnsi;
      pendingAnsi = "";
    }
    inWhitespace = charIsSpace;
    current += char;
    i++;
  }
  if (pendingAnsi) {
    current += pendingAnsi;
  }
  if (current) {
    tokens.push(current);
  }
  return tokens;
}
var TextWrapper = class {
  wrap(text, width) {
    return wrapTextWithAnsi(text, width);
  }
};
var textWrapper = new TextWrapper();
var TokenWrapEngine = {
  wrap(tokens, width, tracker) {
    const wrapped = [];
    let currentLine = "";
    let currentVisibleLength = 0;
    for (const token of tokens) {
      const tokenVisibleLength = visibleWidth(token);
      const isWhitespace = token.trim() === "";
      if (tokenVisibleLength > width && !isWhitespace) {
        if (currentLine) {
          const lineEndReset = tracker.getLineEndReset();
          if (lineEndReset) {
            currentLine += lineEndReset;
          }
          wrapped.push(currentLine);
          currentLine = "";
          currentVisibleLength = 0;
        }
        const broken = splitLongToken(token, width, tracker);
        wrapped.push(...broken.slice(0, -1));
        currentLine = broken[broken.length - 1];
        currentVisibleLength = visibleWidth(currentLine);
        continue;
      }
      const totalNeeded = currentVisibleLength + tokenVisibleLength;
      if (totalNeeded > width && currentVisibleLength > 0) {
        let lineToWrap = currentLine.trimEnd();
        const lineEndReset = tracker.getLineEndReset();
        if (lineEndReset) {
          lineToWrap += lineEndReset;
        }
        wrapped.push(lineToWrap);
        if (isWhitespace) {
          currentLine = tracker.getActiveCodes();
          currentVisibleLength = 0;
        } else {
          currentLine = tracker.getActiveCodes() + token;
          currentVisibleLength = tokenVisibleLength;
        }
      } else {
        currentLine += token;
        currentVisibleLength += tokenVisibleLength;
      }
      mergeTextIntoTracker(token, tracker);
    }
    return { wrapped, currentLine, currentVisibleLength };
  }
};
function wrapTextWithAnsi(text, width) {
  if (!text) {
    return [""];
  }
  const inputLines = text.split("\n");
  const result = [];
  const tracker = new AnsiStateTracker();
  for (const inputLine of inputLines) {
    const prefix = result.length > 0 ? tracker.getActiveCodes() : "";
    result.push(...wrapLinePreservingAnsi(prefix + inputLine, width));
    mergeTextIntoTracker(inputLine, tracker);
  }
  return result.length > 0 ? result : [""];
}
function wrapLinePreservingAnsi(line, width) {
  if (!line) {
    return [""];
  }
  const visibleLength = visibleWidth(line);
  if (visibleLength <= width) {
    return [line];
  }
  const tracker = new AnsiStateTracker();
  const tokens = tokenizeTextWithAnsi(line);
  const { wrapped, currentLine } = TokenWrapEngine.wrap(tokens, width, tracker);
  if (currentLine) {
    wrapped.push(currentLine);
  }
  return wrapped.length > 0 ? wrapped.map((segmentLine) => segmentLine.trimEnd()) : [""];
}
function splitLongToken(word, width, tracker) {
  const lines = [];
  let currentLine = tracker.getActiveCodes();
  let currentWidth = 0;
  let i = 0;
  const segments = [];
  while (i < word.length) {
    const ansiResult = extractAnsiCode(word, i);
    if (ansiResult) {
      segments.push({ type: "ansi", value: ansiResult.code });
      i += ansiResult.length;
    } else {
      let end = i;
      while (end < word.length) {
        const nextAnsi = extractAnsiCode(word, end);
        if (nextAnsi) break;
        end++;
      }
      const textPortion = word.slice(i, end);
      for (const seg of segmenter.segment(textPortion)) {
        segments.push({ type: "grapheme", value: seg.segment });
      }
      i = end;
    }
  }
  for (const seg of segments) {
    if (seg.type === "ansi") {
      currentLine += seg.value;
      tracker.process(seg.value);
      continue;
    }
    const grapheme = seg.value;
    if (!grapheme) continue;
    const clusterWidth = visibleWidth(grapheme);
    if (currentWidth + clusterWidth > width) {
      const lineEndReset = tracker.getLineEndReset();
      if (lineEndReset) {
        currentLine += lineEndReset;
      }
      lines.push(currentLine);
      currentLine = tracker.getActiveCodes();
      currentWidth = 0;
    }
    currentLine += grapheme;
    currentWidth += clusterWidth;
  }
  if (currentLine) {
    lines.push(currentLine);
  }
  return lines.length > 0 ? lines : [""];
}
function truncateToWidth(text, maxWidth, ellipsis = "...", pad = false) {
  const textVisibleWidth = visibleWidth(text);
  if (textVisibleWidth <= maxWidth) {
    return pad ? text + " ".repeat(maxWidth - textVisibleWidth) : text;
  }
  const ellipsisWidth = visibleWidth(ellipsis);
  const targetWidth = maxWidth - ellipsisWidth;
  if (targetWidth <= 0) {
    return ellipsis.substring(0, maxWidth);
  }
  let i = 0;
  const segments = [];
  while (i < text.length) {
    const ansiResult = extractAnsiCode(text, i);
    if (ansiResult) {
      segments.push({ type: "ansi", value: ansiResult.code });
      i += ansiResult.length;
    } else {
      let end = i;
      while (end < text.length) {
        const nextAnsi = extractAnsiCode(text, end);
        if (nextAnsi) break;
        end++;
      }
      const textPortion = text.slice(i, end);
      for (const seg of segmenter.segment(textPortion)) {
        segments.push({ type: "grapheme", value: seg.segment });
      }
      i = end;
    }
  }
  let result = "";
  let currentWidth = 0;
  for (const seg of segments) {
    if (seg.type === "ansi") {
      result += seg.value;
      continue;
    }
    const grapheme = seg.value;
    if (!grapheme) continue;
    const clusterWidth = visibleWidth(grapheme);
    if (currentWidth + clusterWidth > targetWidth) {
      break;
    }
    result += grapheme;
    currentWidth += clusterWidth;
  }
  const truncated = `${result}\x1B[0m${ellipsis}`;
  if (pad) {
    const truncatedWidth = visibleWidth(truncated);
    return truncated + " ".repeat(Math.max(0, maxWidth - truncatedWidth));
  }
  return truncated;
}
var pooledStyleTracker = new AnsiStateTracker();
export {
  CombinedAutocompleteProvider,
  DEFAULT_EDITOR_KEYBINDINGS,
  EditorKeybindingsManager,
  Key,
  fuzzyFilter,
  fuzzyMatch,
  getEditorKeybindings,
  isKeyRelease,
  isKeyRepeat,
  isKittyProtocolActive,
  matchesKey,
  parseKey,
  setEditorKeybindings,
  setKittyProtocolActive,
  truncateToWidth,
  visibleWidth,
  wrapTextWithAnsi
};
