parseUserInput function
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,
);
}