{"version":3,"file":"repl-editor-BG1aDOfH.mjs","names":[],"sources":["../src/cli/query/repl-editor.ts"],"sourcesContent":["import { GraphQLError, Lexer, Source, TokenKind } from \"graphql\";\nimport { getSegments } from \"sql-highlight\";\nimport { assertDefined } from \"#/utils/assert\";\nimport type { TransformEvent, TransformState } from \"@toiroakr/read-multiline\";\n\n// ANSI colour sequences. Kept inline (rather than going through `node:util`\n// styleText) so that tests running outside a TTY still produce deterministic\n// escape sequences regardless of detected colour support.\nconst RESET = \"\\x1b[0m\";\nconst BLUE = \"\\x1b[34m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BRIGHT_GREEN = \"\\x1b[92m\";\nconst CYAN = \"\\x1b[36m\";\nconst BRIGHT_BLUE = \"\\x1b[94m\";\nconst BOLD_CYAN = \"\\x1b[1;36m\";\nconst BOLD_MAGENTA = \"\\x1b[1;35m\";\nconst ITALIC_YELLOW = \"\\x1b[3;33m\";\nconst GREEN = \"\\x1b[32m\";\nconst DIM = \"\\x1b[90m\";\nconst DIM_YELLOW = \"\\x1b[2;33m\";\n\nconst SQL_STYLE_MAP: Record<string, string> = {\n  keyword: BLUE,\n  function: MAGENTA,\n  identifier: YELLOW,\n  string: BRIGHT_GREEN,\n  number: CYAN,\n  bracket: DIM_YELLOW,\n  special: DIM,\n};\n\n/**\n * Highlight a single SQL line using the `sql-highlight` tokenizer.\n * @param line - SQL text for a single editor line\n * @returns ANSI-decorated line safe for terminal output\n */\nexport function highlightSqlLine(line: string): string {\n  const segments = getSegments(line);\n  let result = \"\";\n  for (const seg of segments) {\n    const style = SQL_STYLE_MAP[seg.name];\n    result += style ? style + seg.content + RESET : seg.content;\n  }\n  return result;\n}\n\nconst GQL_KEYWORDS = new Set([\n  \"query\",\n  \"mutation\",\n  \"subscription\",\n  \"fragment\",\n  \"on\",\n  \"type\",\n  \"input\",\n  \"enum\",\n  \"interface\",\n  \"union\",\n  \"scalar\",\n  \"extend\",\n  \"schema\",\n  \"directive\",\n  \"implements\",\n]);\n\n// Keywords that introduce a definition name (next NAME token is the def).\nconst GQL_DEF_KEYWORDS = new Set([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\nconst GQL_BUILTINS = new Set([\"true\", \"false\", \"null\"]);\n\n/**\n * Highlight a single GraphQL line using the official `graphql` Lexer. Tracks\n * paren depth and the previous token to provide semantic-level colouring\n * (field names vs argument names vs types).\n * @param line - GraphQL text for a single editor line\n * @returns ANSI-decorated line, or the input unchanged when the lexer rejects it\n */\nexport function highlightGraphqlLine(line: string): string {\n  if (line.trimStart().startsWith(\"#\")) {\n    return `${DIM}${line}${RESET}`;\n  }\n\n  try {\n    const source = new Source(line);\n    const lexer = new Lexer(source);\n    let result = \"\";\n    let pos = 0;\n\n    let parenDepth = 0;\n    let prevKind: string = \"\";\n    let prevText = \"\";\n    let afterColon = false;\n\n    let token = lexer.advance();\n    while (token.kind !== TokenKind.EOF) {\n      if (token.start > pos) {\n        result += line.slice(pos, token.start);\n      }\n\n      const text = line.slice(token.start, token.end);\n      switch (token.kind) {\n        case TokenKind.NAME: {\n          if (prevKind === TokenKind.DOLLAR) {\n            result += `${MAGENTA}${text}${RESET}`;\n          } else if (prevKind === TokenKind.AT) {\n            result += `${BOLD_MAGENTA}${text}${RESET}`;\n          } else if (GQL_BUILTINS.has(text) || GQL_KEYWORDS.has(text)) {\n            result += `${BLUE}${text}${RESET}`;\n          } else if (GQL_DEF_KEYWORDS.has(prevText)) {\n            result += `${BOLD_CYAN}${text}${RESET}`;\n          } else if (afterColon) {\n            result += `${CYAN}${text}${RESET}`;\n          } else if (parenDepth > 0) {\n            result += `${ITALIC_YELLOW}${text}${RESET}`;\n          } else {\n            result += `${BRIGHT_BLUE}${text}${RESET}`;\n          }\n          afterColon = false;\n          break;\n        }\n        case TokenKind.INT:\n        case TokenKind.FLOAT:\n          result += `${BLUE}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.STRING:\n        case TokenKind.BLOCK_STRING:\n          result += `${GREEN}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.DOLLAR:\n          result += `${MAGENTA}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.AT:\n          result += `${BOLD_MAGENTA}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.BRACE_L:\n        case TokenKind.BRACE_R:\n          result += `${YELLOW}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.PAREN_L:\n          parenDepth += 1;\n          result += `${DIM_YELLOW}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.PAREN_R:\n          parenDepth = Math.max(0, parenDepth - 1);\n          result += `${DIM_YELLOW}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.BRACKET_L:\n        case TokenKind.BRACKET_R:\n          result += `${DIM_YELLOW}${text}${RESET}`;\n          afterColon = false;\n          break;\n        case TokenKind.COLON:\n          result += `${DIM}${text}${RESET}`;\n          afterColon = true;\n          break;\n        case TokenKind.BANG:\n        case TokenKind.EQUALS:\n        case TokenKind.PIPE:\n        case TokenKind.AMP:\n        case TokenKind.SPREAD:\n          result += `${DIM}${text}${RESET}`;\n          afterColon = false;\n          break;\n        default:\n          result += text;\n          afterColon = false;\n      }\n\n      prevKind = token.kind;\n      prevText = text;\n      pos = token.end;\n      token = lexer.advance();\n    }\n\n    if (pos < line.length) {\n      result += line.slice(pos);\n    }\n    return result;\n  } catch (error) {\n    // The lexer throws GraphQLError on partial or invalid input (e.g. an\n    // unterminated string while the user is still typing). Fall back to the\n    // raw line so the editor keeps rendering without colour until the input\n    // is valid. Any other error is a real bug and should surface.\n    if (error instanceof GraphQLError) {\n      return line;\n    }\n    throw error;\n  }\n}\n\nconst BRACKET_PAIRS: Record<string, string> = { \"(\": \")\", \"[\": \"]\", \"{\": \"}\" };\nconst CLOSE_BRACKETS = new Set(Object.values(BRACKET_PAIRS));\n\n/**\n * Apply auto-close brackets and auto-indent on newline. Works for both SQL\n * and GraphQL because both languages share the `()`, `[]`, `{}` bracket set.\n * @param state - Editor state after the last edit\n * @param event - Event describing the edit that just occurred\n * @returns A new editor state to apply, or `undefined` to leave the state unchanged\n */\nexport function replTransform(\n  state: TransformState,\n  event: TransformEvent,\n): TransformState | undefined {\n  const { lines, row, col } = state;\n\n  if (event.type === \"insert\" && event.char in BRACKET_PAIRS) {\n    const close = BRACKET_PAIRS[event.char];\n    const line = assertDefined(lines[row], `line at row ${row} missing`);\n    const newLine = line.slice(0, col) + close + line.slice(col);\n    return { lines: lines.with(row, newLine), row, col };\n  }\n\n  if (event.type === \"insert\" && CLOSE_BRACKETS.has(event.char)) {\n    const line = assertDefined(lines[row], `line at row ${row} missing`);\n    if (line[col] === event.char) {\n      const newLine = line.slice(0, col) + line.slice(col + 1);\n      return { lines: lines.with(row, newLine), row, col };\n    }\n  }\n\n  if (event.type === \"backspace\") {\n    const line = assertDefined(lines[row], `line at row ${row} missing`);\n    const beforeCursor = line.slice(0, col);\n    if (beforeCursor.length >= 1 && /^ +$/.test(beforeCursor)) {\n      const newIndent = beforeCursor.slice(0, -1);\n      const newLine = newIndent + line.slice(col);\n      return { lines: lines.with(row, newLine), row, col: newIndent.length };\n    }\n  }\n\n  if (event.type === \"newline\" && row > 0) {\n    const prevLine = assertDefined(lines[row - 1], `line at row ${row - 1} missing`);\n    const baseIndent = prevLine.match(/^(\\s*)/)?.[1] ?? \"\";\n    const endsWithOpen = /[{([]$/.test(prevLine.trimEnd());\n    const currentLine = assertDefined(lines[row], `line at row ${row} missing`);\n    const startsWithClose = /^[}\\])]/.test(currentLine.trimStart());\n\n    if (endsWithOpen && startsWithClose) {\n      // Bracket expansion: the cursor sits between a matching open/close\n      // pair (e.g. `{|}`). Expand into a three-line block with the cursor\n      // on an indented middle line.\n      const innerIndent = baseIndent + \"  \";\n      const newLines = lines\n        .with(row, innerIndent)\n        .toSpliced(row + 1, 0, baseIndent + currentLine.trimStart());\n      return { lines: newLines, row, col: innerIndent.length };\n    }\n    if (endsWithOpen) {\n      // A lone open bracket on the previous line: drop an extra indent for\n      // the new line and auto-insert the matching closing bracket below.\n      const openChar = prevLine.trimEnd().slice(-1);\n      const closeChar = BRACKET_PAIRS[openChar] ?? \"}\";\n      const indent = baseIndent + \"  \";\n      const newLines = lines\n        .with(row, indent + currentLine)\n        .toSpliced(row + 1, 0, baseIndent + closeChar);\n      return { lines: newLines, row, col: indent.length };\n    }\n    if (baseIndent && col === 0) {\n      return { lines: lines.with(row, baseIndent + currentLine), row, col: baseIndent.length };\n    }\n  }\n\n  return undefined;\n}\n"],"mappings":"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"}