Temporary branch to try formatting in isolates. This uses Isolate.run() to run the formatting logic on separate isolates. It also uses a Pool to ensure we don't try to load too many files in parallel and run out of descriptors.
diff --git a/lib/src/cli/format_command.dart b/lib/src/cli/format_command.dart index 99a8135..3dd2d7c 100644 --- a/lib/src/cli/format_command.dart +++ b/lib/src/cli/format_command.dart
@@ -5,6 +5,7 @@ import 'package:args/command_runner.dart'; +import '../format_service.dart'; import '../io.dart'; import '../short/style_fix.dart'; import 'formatter_options.dart'; @@ -153,7 +154,10 @@ if (argResults.rest.isEmpty) { await formatStdin(options, selection, stdinName); } else { - formatPaths(options, argResults.rest); + var service = FormatService(options); + await service.format(argResults.rest); + + // await formatPaths(options, argResults.rest); options.summary.show(); }
diff --git a/lib/src/format_service.dart b/lib/src/format_service.dart new file mode 100644 index 0000000..6b3b2bf --- /dev/null +++ b/lib/src/format_service.dart
@@ -0,0 +1,144 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:path/path.dart' as p; +import 'package:pool/pool.dart'; + +import 'cli/formatter_options.dart'; +import 'dart_formatter.dart'; +import 'exceptions.dart'; +import 'profile.dart'; +import 'source_code.dart'; + +class FormatService { + final FormatterOptions _options; + + FormatService(this._options); + + /// Formats all of the files and directories given by [paths]. + Future<void> format(List<String> paths) async { + Profile.begin('FormatService.format()'); + + Profile.begin('list paths'); + var files = <File>[]; + for (var path in paths) { + var directory = Directory(path); + if (await directory.exists()) { + files.addAll(await _listDirectory(directory)); + continue; + } + + var file = File(path); + if (await file.exists()) { + files.add(file); + } else { + stderr.writeln('No file or directory found at "$path".'); + } + } + Profile.end('list paths'); + + // TODO: Tune? + var pool = Pool(Platform.numberOfProcessors); + for (var file in files) { + unawaited(pool.withResource(() async { + await _format(file); + })); + } + + await pool.close(); + + Profile.end('FormatService.format()'); + Profile.report(); + } + + Future<List<File>> _listDirectory(Directory directory) async { + var files = <File>[]; + _options.showDirectory(directory.path); + + var shownHiddenPaths = <String>{}; + + var entries = + directory.listSync(recursive: true, followLinks: _options.followLinks); + entries.sort((a, b) => a.path.compareTo(b.path)); + + for (var entry in entries) { + var displayPath = _options.show.displayPath(directory.path, entry.path); + + if (entry is Link) { + _options.showSkippedLink(displayPath); + continue; + } + + if (entry is! File || !entry.path.endsWith('.dart')) continue; + + // If the path is in a subdirectory starting with ".", ignore it. + var parts = p.split(p.relative(entry.path, from: directory.path)); + int? hiddenIndex; + for (var i = 0; i < parts.length; i++) { + if (parts[i].startsWith('.')) { + hiddenIndex = i; + break; + } + } + + if (hiddenIndex != null) { + // Since we'll hide everything inside the directory starting with ".", + // show the directory name once instead of once for each file. + var hiddenPath = p.joinAll(parts.take(hiddenIndex + 1)); + if (shownHiddenPaths.add(hiddenPath)) { + _options.showHiddenPath(hiddenPath); + } + continue; + } + + files.add(entry); + } + + return files; + } + + Future<void> _format(File file) async { + // TODO: Preserve old logic. + var displayPath = file.path; + + var formatter = DartFormatter( + indent: _options.indent, + pageWidth: _options.pageWidth, + fixes: _options.fixes, + experimentFlags: _options.experimentFlags); + + try { + var source = SourceCode(await file.readAsString(), uri: file.path); + _options.beforeFile(file, displayPath); + + var output = await Isolate.run(() async { + return formatter.formatSource(source); + }); + + _options.afterFile(file, displayPath, output, + changed: source.text != output.text); + return; + } on FormatterException catch (err) { + var color = Platform.operatingSystem != 'windows' && + stdioType(stderr) == StdioType.terminal; + + stderr.writeln(err.message(color: color)); + } on UnexpectedOutputException catch (err) { + stderr.writeln('''Hit a bug in the formatter when formatting $displayPath. +$err +Please report at github.com/dart-lang/dart_style/issues.'''); + } catch (err, stack) { + stderr.writeln('''Hit a bug in the formatter when formatting $displayPath. +Please report at github.com/dart-lang/dart_style/issues. +$err +$stack'''); + } + + // If we get here, some error occurred. + exitCode = 65; + } +}
diff --git a/pubspec.yaml b/pubspec.yaml index 9aa6af0..c0fde10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -13,6 +13,7 @@ args: ">=1.0.0 <3.0.0" collection: "^1.17.0" path: ^1.0.0 + pool: ^1.5.1 pub_semver: ">=1.4.4 <3.0.0" source_span: ^1.4.0