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);}
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);
}