searchGenerator top-level property
Implementation
final FigGenerator searchGenerator = FigGenerator(
trigger: (oldTokens, newTokens) {
// Basic trigger logic: trigger if @ count changes
// Simplified for Dart as dynamic trigger function is tricky,
// but the spec supports 'trigger' string.
// For now we will rely on default behavior or implement custom trigger if needed.
// The TS code uses a function to detect @ changes.
return '@';
},
getQueryTerm: '@',
custom: (tokens, executeShellCommand, context) async {
if (executeShellCommand == null || context == null) return [];
// Intl number format simple implementation
String formatNumber(int n) {
if (n >= 1000000) return '${(n / 1000000).toStringAsFixed(1)}M';
if (n >= 1000) return '${(n / 1000).toStringAsFixed(1)}K';
return n.toString();
}
// Last token from context logic is tricky because tokens passed here are usually the full line
// but 'context' in TS had specific meaning.
// Here `tokens` are usually the arguments.
// Let's assume the last token is what we are typing.
// In Fig, `context` argument to script is an array of tokens.
// In Dart spec, `tokens` is the array of tokens.
if (tokens.isEmpty) return [];
final lastToken = tokens.last;
if (lastToken.contains('@') && !lastToken.startsWith('@')) {
final parts = lastToken.split('@');
final crate = parts[0];
// final version = parts.length > 1 ? parts[1] : '';
final query = Uri.encodeComponent(crate);
final output = await executeShellCommand(ExecuteCommandInput(
command: 'curl',
args: ['-sfL', 'https://crates.io/api/v1/crates/$query/versions'],
));
try {
final json = jsonDecode(output.stdout);
final versions =
(json['versions'] as List).map((e) => Version.fromJson(e)).toList();
return versions
.map((version) => FigSuggestion(
name: '$crate@${version.num}',
insertValue: version.num,
description:
'${formatNumber(version.downloads)} downloads - ${version.createdAt.split('T')[0]}',
hidden: version.yanked,
))
.toList();
} catch (e) {
return [];
}
} else if (lastToken.isNotEmpty) {
final query = Uri.encodeComponent(lastToken);
// Parallel execution
final results = await Future.wait<ExecuteCommandOutput>([
executeShellCommand(ExecuteCommandInput(
command: 'curl',
args: [
'-sfL',
'https://crates.io/api/v1/crates?q=$query&per_page=60',
],
)),
executeShellCommand(ExecuteCommandInput(
command: 'cargo',
args: ['metadata', '--format-version', '1', '--no-deps'],
)),
]);
final remoteStdout = results[0].stdout;
final localStdout = results[1].stdout;
List<FigSuggestion> remoteSuggestions = [];
try {
final remoteJson = jsonDecode(remoteStdout);
final crates = (remoteJson['crates'] as List)
.map((e) => Crate.fromJson(e))
.toList();
crates.sort((a, b) => b.recentDownloads.compareTo(a.recentDownloads));
remoteSuggestions = crates
.map((crate) => FigSuggestion(
icon: '📦',
displayName: '${crate.name}@${crate.newestVersion}',
name: crate.name,
description:
'${formatNumber(crate.recentDownloads)}${crate.description != null ? ' - ${crate.description}' : ''}',
))
.toList();
} catch (e) {
// Ignore parsing errors
}
List<FigSuggestion> localSuggestions = [];
if (localStdout.trim().isNotEmpty) {
try {
final localJson = Metadata.fromJson(jsonDecode(localStdout));
localSuggestions = localJson.packages
.where((pkg) => pkg.source == null)
.map((pkg) => FigSuggestion(
icon: '📦',
displayName: '${pkg.name}@${pkg.version}',
name: pkg.name,
description:
'Local Crate ${pkg.version}${pkg.description != null ? ' - ${pkg.description}' : ''}',
))
.toList();
} catch (e) {
// Ignore
}
}
return [...remoteSuggestions, ...localSuggestions];
} else {
return [];
}
},
);