| import 'dart:io'; |
| |
| import 'package:logging/logging.dart'; |
| import 'package:path/path.dart' as p; |
| |
| import 'util.dart'; |
| |
| /// The string 'completion' used to denote that arguments provided to an app are |
| /// for command completion. |
| /// |
| /// The expected arg format is: completion -- {process name} {rest of current |
| /// args} |
| const String completionCommandName = 'completion'; |
| |
| const _compPointVar = 'COMP_POINT'; |
| |
| void tryCompletion( |
| List<String> args, |
| List<String> Function(List<String> args, String compLine, int compPoint) |
| completer, { |
| @Deprecated('Useful for testing, but do not release with this set.') |
| bool? logFile, |
| }) { |
| if (logFile ?? false) { |
| final logFile = File('_completion.log'); |
| |
| void logLine(String content) { |
| logFile.writeAsStringSync('$content\n', mode: FileMode.writeOnlyAppend); |
| } |
| |
| logLine(' *' * 50); |
| |
| Logger.root.onRecord.listen((e) { |
| final loggerName = e.loggerName.split('.'); |
| if (loggerName.isNotEmpty && loggerName.first == 'completion') { |
| loggerName.removeAt(0); |
| assert(e.level == Level.INFO); |
| logLine( |
| '${loggerName.join('.').padLeft(Tag.longestTagLength)} ' |
| '${e.message}', |
| ); |
| } |
| }); |
| } |
| |
| String scriptName; |
| try { |
| scriptName = p.basename(Platform.script.toFilePath()); |
| } on FileSystemException { |
| // Script may not have a current working directory. |
| scriptName = '<unknown>'; |
| } on UnsupportedError // ignore: avoid_catching_errors |
| { |
| scriptName = '<unknown>'; |
| } |
| |
| log('Checking for completion on script:\t$scriptName'); |
| if (args.length >= 3 && args[0] == completionCommandName && args[1] == '--') { |
| try { |
| log('Starting completion'); |
| log('All args: $args'); |
| log('completion-reported exe: ${args[2]}'); |
| |
| final env = Platform.environment; |
| |
| // There are 3 interesting env parameters passed by the completion logic |
| // COMP_LINE: the full contents of the completion |
| final compLine = env['COMP_LINE']; |
| if (compLine == null) { |
| throw StateError('Environment variable COMP_LINE must be set'); |
| } |
| |
| // COMP_CWORD: number of words. Also might be nice |
| // COMP_POINT: where the cursor is on the completion line |
| final compPointValue = env[_compPointVar]; |
| if (compPointValue == null || compPointValue.isEmpty) { |
| throw StateError( |
| 'Environment variable $_compPointVar must be set and non-empty'); |
| } |
| final compPoint = int.tryParse(compPointValue); |
| |
| if (compPoint == null) { |
| throw FormatException('Could not parse $_compPointVar value ' |
| '"$compPointValue" into an integer'); |
| } |
| |
| final trimmedArgs = args.sublist(3); |
| |
| log('input args: ${helpfulToString(trimmedArgs)}'); |
| |
| final completions = completer(trimmedArgs, compLine, compPoint); |
| |
| log('completions: ${helpfulToString(completions)}'); |
| |
| for (final comp in completions) { |
| print(comp); |
| } |
| exit(0); |
| } catch (ex, stack) { |
| log('An error occurred while attemping completion'); |
| log(ex); |
| log(stack); |
| exit(1); |
| } |
| } |
| |
| log('Completion params not found'); |
| } |