214 lines · 5.8 KB · TypeScript
RawDownload
import {  detectComposerTrigger as detectComposerTriggerBase,  type ComposerTrigger,  type ComposerTriggerKind,  type ComposerSlashCommand,} from "@t3tools/shared/composerTrigger";export {  replaceTextRange,  parseStandaloneComposerSlashCommand,} from "@t3tools/shared/composerTrigger";export type { ComposerTrigger, ComposerTriggerKind, ComposerSlashCommand }; import { splitPromptIntoComposerSegments } from "./composer-editor-mentions";import { INLINE_TERMINAL_CONTEXT_PLACEHOLDER } from "./lib/terminalContext"; const isInlineTokenSegment = (  segment:    | { type: "text"; text: string }    | { type: "mention" }    | { type: "skill" }    | { type: "terminal-context" },): boolean => segment.type !== "text"; function clampCursor(text: string, cursor: number): number {  if (!Number.isFinite(cursor)) return text.length;  return Math.max(0, Math.min(text.length, Math.floor(cursor)));} function isWhitespace(char: string): boolean {  return (    char === " " ||    char === "\n" ||    char === "\t" ||    char === "\r" ||    char === INLINE_TERMINAL_CONTEXT_PLACEHOLDER  );} export function expandCollapsedComposerCursor(text: string, cursorInput: number): number {  const collapsedCursor = clampCursor(text, cursorInput);  const segments = splitPromptIntoComposerSegments(text);  if (segments.length === 0) {    return collapsedCursor;  }   let remaining = collapsedCursor;  let expandedCursor = 0;   for (const segment of segments) {    if (segment.type === "mention") {      const expandedLength = segment.path.length + 1;      if (remaining <= 1) {        return expandedCursor + (remaining === 0 ? 0 : expandedLength);      }      remaining -= 1;      expandedCursor += expandedLength;      continue;    }    if (segment.type === "skill") {      const expandedLength = segment.name.length + 1;      if (remaining <= 1) {        return expandedCursor + (remaining === 0 ? 0 : expandedLength);      }      remaining -= 1;      expandedCursor += expandedLength;      continue;    }    if (segment.type === "terminal-context") {      if (remaining <= 1) {        return expandedCursor + remaining;      }      remaining -= 1;      expandedCursor += 1;      continue;    }     const segmentLength = segment.text.length;    if (remaining <= segmentLength) {      return expandedCursor + remaining;    }    remaining -= segmentLength;    expandedCursor += segmentLength;  }   return expandedCursor;} function collapsedSegmentLength(  segment:    | { type: "text"; text: string }    | { type: "mention" }    | { type: "skill" }    | { type: "terminal-context" },): number {  if (segment.type === "text") {    return segment.text.length;  }  return 1;} function clampCollapsedComposerCursorForSegments(  segments: ReadonlyArray<    | { type: "text"; text: string }    | { type: "mention" }    | { type: "skill" }    | { type: "terminal-context" }  >,  cursorInput: number,): number {  const collapsedLength = segments.reduce(    (total, segment) => total + collapsedSegmentLength(segment),    0,  );  if (!Number.isFinite(cursorInput)) {    return collapsedLength;  }  return Math.max(0, Math.min(collapsedLength, Math.floor(cursorInput)));} export function clampCollapsedComposerCursor(text: string, cursorInput: number): number {  return clampCollapsedComposerCursorForSegments(    splitPromptIntoComposerSegments(text),    cursorInput,  );} export function collapseExpandedComposerCursor(text: string, cursorInput: number): number {  const expandedCursor = clampCursor(text, cursorInput);  const segments = splitPromptIntoComposerSegments(text);  if (segments.length === 0) {    return expandedCursor;  }   let remaining = expandedCursor;  let collapsedCursor = 0;   for (const segment of segments) {    if (segment.type === "mention") {      const expandedLength = segment.path.length + 1;      if (remaining === 0) {        return collapsedCursor;      }      if (remaining <= expandedLength) {        return collapsedCursor + 1;      }      remaining -= expandedLength;      collapsedCursor += 1;      continue;    }    if (segment.type === "skill") {      const expandedLength = segment.name.length + 1;      if (remaining === 0) {        return collapsedCursor;      }      if (remaining <= expandedLength) {        return collapsedCursor + 1;      }      remaining -= expandedLength;      collapsedCursor += 1;      continue;    }    if (segment.type === "terminal-context") {      if (remaining <= 1) {        return collapsedCursor + remaining;      }      remaining -= 1;      collapsedCursor += 1;      continue;    }     const segmentLength = segment.text.length;    if (remaining <= segmentLength) {      return collapsedCursor + remaining;    }    remaining -= segmentLength;    collapsedCursor += segmentLength;  }   return collapsedCursor;} export function isCollapsedCursorAdjacentToInlineToken(  text: string,  cursorInput: number,  direction: "left" | "right",): boolean {  const segments = splitPromptIntoComposerSegments(text);  if (!segments.some(isInlineTokenSegment)) {    return false;  }   const cursor = clampCollapsedComposerCursorForSegments(segments, cursorInput);  let collapsedOffset = 0;   for (const segment of segments) {    if (isInlineTokenSegment(segment)) {      if (direction === "left" && cursor === collapsedOffset + 1) {        return true;      }      if (direction === "right" && cursor === collapsedOffset) {        return true;      }    }    collapsedOffset += collapsedSegmentLength(segment);  }   return false;} export const isCollapsedCursorAdjacentToMention = isCollapsedCursorAdjacentToInlineToken; export function detectComposerTrigger(text: string, cursorInput: number): ComposerTrigger | null {  return detectComposerTriggerBase(text, cursorInput, isWhitespace);}