From ac3aa6dad6b221ca361e6d012ba34e1721b45fcf Mon Sep 17 00:00:00 2001 From: Jaime Wren Date: Fri, 13 Feb 2026 15:19:59 -0800 Subject: [PATCH] refactor: Centralize table formatting logic into a new `formatTable` utility function. (#182196) This resolves https://github.com/flutter/flutter/issues/180949 This is follow-up on https://github.com/flutter/flutter/pull/180098 --- .../widget_preview_scaffold/pubspec.yaml | 4 +- .../flutter_tools/lib/src/base/utils.dart | 35 +++++++++++++ .../lib/src/commands/running_apps.dart | 18 +------ packages/flutter_tools/lib/src/device.dart | 16 +----- packages/flutter_tools/pubspec.yaml | 8 +-- .../test/general.shard/utils_test.dart | 49 +++++++++++++++++++ pubspec.lock | 12 ++--- pubspec.yaml | 8 +-- 8 files changed, 102 insertions(+), 48 deletions(-) diff --git a/dev/integration_tests/widget_preview_scaffold/pubspec.yaml b/dev/integration_tests/widget_preview_scaffold/pubspec.yaml index 27a38dc402f..a505408293b 100644 --- a/dev/integration_tests/widget_preview_scaffold/pubspec.yaml +++ b/dev/integration_tests/widget_preview_scaffold/pubspec.yaml @@ -45,7 +45,7 @@ dependencies: term_glyph: 1.2.2 test_api: 0.7.9 typed_data: 1.4.0 - unified_analytics: 8.0.10 + unified_analytics: 8.0.11 url_launcher_android: 6.3.28 url_launcher_ios: 6.4.0 url_launcher_linux: 3.2.2 @@ -68,4 +68,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: nepob0 +# PUBSPEC CHECKSUM: fv45lk diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index d71877f5f6f..53edaf884e5 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -611,3 +611,38 @@ extension StackTraceTransform on Stream { final utf8LineDecoder = StreamTransformer, String>.fromBind( (stream) => stream.transformWithCallSite(utf8.decoder).transform(const LineSplitter()), ); + +/// Formats a list of rows into a table with aligned columns. +/// +/// [table] is a list of rows, where each row is a list of strings. +/// [separator] is the string used to separate columns (default is ' • '). +/// +/// Returns a list of strings, where each string is a formatted row. +List formatTable(List> table, {String separator = ' • ', int indent = 0}) { + if (table.isEmpty) { + return []; + } + + // Calculate column widths + if (table.first.isEmpty) { + throw Exception('Table header cannot be empty'); + } + final indices = List.generate(table.first.length - 1, (int i) => i); + final widths = List.filled(indices.length, 0); + for (final row in table) { + for (final i in indices) { + widths[i] = math.max(widths[i], row[i].length); + } + } + + final String indentString = ' ' * indent; + + // Join columns into lines of text + return table.map((List row) { + final String formatted = indices + .map((int i) => row[i].padRight(widths[i])) + .followedBy([row.last]) + .join(separator); + return '$indentString$formatted'; + }).toList(); +} diff --git a/packages/flutter_tools/lib/src/commands/running_apps.dart b/packages/flutter_tools/lib/src/commands/running_apps.dart index 182ac77045a..ba608200618 100644 --- a/packages/flutter_tools/lib/src/commands/running_apps.dart +++ b/packages/flutter_tools/lib/src/commands/running_apps.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:meta/meta.dart'; import 'package:multicast_dns/multicast_dns.dart'; @@ -118,23 +117,8 @@ class RunningAppsCommand extends FlutterCommand { table.add(['$projectName ($mode)', deviceString, platform, vmServiceUri, age]); } - // TODO(jwren): consider combining this logic with the logic in `flutter devices`, - // see https://github.com/flutter/flutter/issues/180949 - // Calculate column widths - final indices = List.generate(table[0].length - 1, (int i) => i); - List widths = indices.map((int i) => 0).toList(); - for (final row in table) { - widths = indices.map((int i) => math.max(widths[i], row[i].length)).toList(); - } - // Join columns into lines of text - for (final row in table) { - final String rowString = indices - .map((int i) => row[i].padRight(widths[i])) - .followedBy([row.last]) - .join(' • '); - _logger.printStatus(' $rowString'); - } + _logger.printStatus(formatTable(table, indent: 2).join('\n')); return FlutterCommandResult.success(); } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 75a4784c83c..f67e69b67c8 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:meta/meta.dart'; @@ -859,21 +858,8 @@ abstract class Device { ]); } - // Calculate column widths - final indices = List.generate(table[0].length - 1, (int i) => i); - List widths = indices.map((int i) => 0).toList(); - for (final row in table) { - widths = indices.map((int i) => math.max(widths[i], row[i].length)).toList(); - } - // Join columns into lines of text - return [ - for (final List row in table) - indices - .map((int i) => row[i].padRight(widths[i])) - .followedBy([row.last]) - .join(' • '), - ]; + return formatTable(table); } static Future printDevices( diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index cc892aef983..ea4c1176b4d 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: completion: 1.0.2 coverage: 1.15.0 crypto: 3.0.7 - ffi: 2.1.5 + ffi: 2.2.0 file: 7.0.1 flutter_template_images: 5.0.0 html: 0.15.6 @@ -54,7 +54,7 @@ dependencies: http_multi_server: 3.2.2 convert: 3.1.2 async: 2.13.0 - unified_analytics: 8.0.10 + unified_analytics: 8.0.11 pubspec_parse: 1.5.0 graphs: 2.3.2 @@ -113,7 +113,7 @@ dependencies: watcher: 1.2.1 web: 1.1.1 web_socket: 1.0.1 - yaml_edit: 2.2.3 + yaml_edit: 2.2.4 mdns_dart: ^2.1.0 dev_dependencies: @@ -129,4 +129,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 8kslb0 +# PUBSPEC CHECKSUM: j77gl7 diff --git a/packages/flutter_tools/test/general.shard/utils_test.dart b/packages/flutter_tools/test/general.shard/utils_test.dart index 6122452b386..6d14033d6b9 100644 --- a/packages/flutter_tools/test/general.shard/utils_test.dart +++ b/packages/flutter_tools/test/general.shard/utils_test.dart @@ -568,4 +568,53 @@ needs to be wrapped. await validCompleter.future; await errorCompleter.future; }); + + testWithoutContext('formatTable', () { + expect(formatTable(>[]), isEmpty); + + expect(() => formatTable(>[[]]), throwsException); + expect(() => formatTable(>[[], []]), throwsException); + + expect( + formatTable(>[ + ['Col1', 'Col2'], + ['Value1', 'Value2'], + ]).join('\n'), + ''' +Col1 • Col2 +Value1 • Value2''', + ); + + expect( + formatTable(>[ + ['A', 'B', 'C'], + ['LongValue', 'Short', 'Tiny'], + ]).join('\n'), + ''' +A • B • C +LongValue • Short • Tiny''', + ); + + expect( + formatTable(>[ + ['A', 'B'], + ['1', '2'], + ], separator: ' | ').join('\n'), + ''' +A | B +1 | 2''', + ); + }); + + testWithoutContext('formatTable with indent', () { + expect( + formatTable(>[ + ['A', 'B'], + ['1', '2'], + ], indent: 2).join('\n'), + ''' + A • B + 1 • 2''', + ); + }); } diff --git a/pubspec.lock b/pubspec.lock index c3353d16acb..3a4bd69ae21 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -214,10 +214,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" ffigen: dependency: "direct dev" description: @@ -955,10 +955,10 @@ packages: dependency: "direct main" description: name: video_player_avfoundation - sha256: f46e9e20f1fe429760cf4dc118761336320d1bec0f50d255930c2355f2defb5b + sha256: f93b93a3baa12ca0ff7d00ca8bc60c1ecd96865568a01ff0c18a99853ee201a5 url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.3" video_player_platform_interface: dependency: "direct main" description: @@ -1099,10 +1099,10 @@ packages: dependency: "direct main" description: name: yaml_edit - sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3 + sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" sdks: dart: ">=3.10.3 <4.0.0" flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 574896e31b9..58a23b96404 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,7 +101,7 @@ dependencies: device_info: 2.0.3 device_info_platform_interface: 2.0.1 fake_async: 1.3.3 - ffi: 2.1.5 + ffi: 2.2.0 file: 7.0.1 fixnum: 1.1.1 flutter_gallery_assets: 1.0.2 @@ -186,7 +186,7 @@ dependencies: vector_math: 2.2.0 video_player: 2.10.1 video_player_android: 2.9.1 - video_player_avfoundation: 2.9.1 + video_player_avfoundation: 2.9.3 video_player_platform_interface: 6.6.0 video_player_web: 2.4.0 vm_service: 15.0.2 @@ -211,9 +211,9 @@ dependencies: lints: 6.1.0 pedantic: 1.11.1 quiver: 3.2.2 - yaml_edit: 2.2.3 + yaml_edit: 2.2.4 dev_dependencies: ffigen: 20.1.1 -# PUBSPEC CHECKSUM: q8irkm +# PUBSPEC CHECKSUM: r03o20