parseUserInput function

ParsedUserInput parseUserInput(
  1. String input, {
  2. String? workingDirectory,
})

Parse user input into structured segments.

Implementation

ParsedUserInput parseUserInput(String input, {String? workingDirectory}) {
  final wd = workingDirectory ?? Directory.current.path;
  final normalized = normalizeInput(input);

  if (normalized.isEmpty) {
    return ParsedUserInput(
      rawInput: input,
      segments: const [],
      mentionedFiles: const [],
      mentionedUrls: const [],
      isCommand: false,
      isEmpty: true,
      isMultiline: false,
      lineCount: 0,
    );
  }

  // Detect slash command.
  final isCmd = isSlashCommand(normalized);
  String? cmdName;
  String? cmdArgs;
  if (isCmd) {
    final parsed = parseSlashCommand(normalized);
    if (parsed != null) {
      cmdName = parsed.command;
      cmdArgs = parsed.args.isEmpty ? null : parsed.args;
    }
  }

  // Build segments by scanning for @-mentions.
  final segments = <InputSegment>[];
  final mentionedFiles = <String>[];
  final mentionedUrls = <String>[];

  // Collect all mention matches with their spans.
  final mentions = <_MentionMatch>[];

  for (final m in gitRefPattern.allMatches(normalized)) {
    final kind = m.group(1)!; // branch, commit, tag
    final ref = m.group(2)!;
    mentions.add(
      _MentionMatch(
        start: m.start,
        end: m.end,
        segment: GitRefMention('$kind:$ref'),
      ),
    );
  }

  for (final m in urlMentionPattern.allMatches(normalized)) {
    final url = m.group(1)!;
    // Skip if overlapping with an earlier match.
    if (_overlaps(mentions, m.start, m.end)) continue;
    mentionedUrls.add(url);
    mentions.add(
      _MentionMatch(start: m.start, end: m.end, segment: UrlMention(url)),
    );
  }

  for (final m in fileMentionPattern.allMatches(normalized)) {
    if (_overlaps(mentions, m.start, m.end)) continue;
    final raw = m.group(1)!;
    final resolved = resolveFilePath(raw, wd);
    final isDir = _looksLikeDirectory(raw);
    mentionedFiles.add(resolved);
    mentions.add(
      _MentionMatch(
        start: m.start,
        end: m.end,
        segment: FileMention(
          path: resolved,
          originalText: '@$raw',
          isDirectory: isDir,
        ),
      ),
    );
  }

  // Sort mentions by position.
  mentions.sort((a, b) => a.start.compareTo(b.start));

  // Interleave text and mention segments.
  var cursor = 0;
  for (final mm in mentions) {
    if (mm.start > cursor) {
      segments.add(TextSegment(normalized.substring(cursor, mm.start)));
    }
    segments.add(mm.segment);
    cursor = mm.end;
  }
  if (cursor < normalized.length) {
    segments.add(TextSegment(normalized.substring(cursor)));
  }

  // If the input is a command and we haven't added a CommandReference segment,
  // wrap the whole thing.
  if (isCmd && cmdName != null) {
    segments.insert(0, CommandReference(commandName: cmdName, args: cmdArgs));
  }

  final lines = normalized.split('\n');

  return ParsedUserInput(
    rawInput: input,
    segments: segments,
    mentionedFiles: mentionedFiles,
    mentionedUrls: mentionedUrls,
    isCommand: isCmd,
    commandName: cmdName,
    commandArgs: cmdArgs,
    isEmpty: false,
    isMultiline: lines.length > 1,
    lineCount: lines.length,
  );
}