diff --git a/pkgs/dart_services/lib/server.dart b/pkgs/dart_services/lib/server.dart index 563093ca3..df6579a3c 100644 --- a/pkgs/dart_services/lib/server.dart +++ b/pkgs/dart_services/lib/server.dart @@ -20,15 +20,27 @@ import 'src/sdk.dart'; final Logger _logger = Logger('services'); Future main(List args) async { - final parser = ArgParser() - ..addOption('port', valueHelp: 'port', help: 'The port to listen on.') - ..addOption('redis-url', valueHelp: 'url', help: 'The redis server url.') - ..addOption('storage-bucket', - valueHelp: 'name', - help: 'The name of the Cloud Storage bucket for compilation artifacts.', - defaultsTo: 'nnbd_artifacts') - ..addFlag('help', - abbr: 'h', negatable: false, help: 'Show this usage information.'); + final parser = + ArgParser() + ..addOption('port', valueHelp: 'port', help: 'The port to listen on.') + ..addOption( + 'redis-url', + valueHelp: 'url', + help: 'The redis server url.', + ) + ..addOption( + 'storage-bucket', + valueHelp: 'name', + help: + 'The name of the Cloud Storage bucket for compilation artifacts.', + defaultsTo: 'nnbd_artifacts', + ) + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Show this usage information.', + ); final results = parser.parse(args); if (results['help'] as bool) { @@ -66,18 +78,23 @@ Future main(List args) async { .map((entry) => '${entry.key}:${entry.value}') .join(','); - _logger.info(''' + _logger.info( + ''' Starting dart-services: port: $port sdkPath: ${sdk.dartSdkPath} redisServerUri: $redisServerUri - Cloud Run Environment variables: $cloudRunEnvVars''' - .trim()); + Cloud Run Environment variables: $cloudRunEnvVars'''.trim(), + ); await GitHubOAuthHandler.initFromEnvironmentalVars(); - final server = - await EndpointsServer.serve(port, sdk, redisServerUri, storageBucket); + final server = await EndpointsServer.serve( + port, + sdk, + redisServerUri, + storageBucket, + ); _logger.info('Listening on port ${server.port}'); } @@ -89,8 +106,11 @@ class EndpointsServer { String? redisServerUri, String storageBucket, ) async { - final endpointsServer = - EndpointsServer._(sdk, redisServerUri, storageBucket); + final endpointsServer = EndpointsServer._( + sdk, + redisServerUri, + storageBucket, + ); await endpointsServer._init(); endpointsServer.server = await shelf.serve( @@ -113,15 +133,14 @@ class EndpointsServer { // https://cloud.google.com/run/docs/reference/container-contract#env-vars final serverVersion = Platform.environment['K_REVISION']; - final cache = redisServerUri == null - ? NoopCache() - : RedisCache(redisServerUri, sdk, serverVersion); + final cache = + redisServerUri == null + ? NoopCache() + : RedisCache(redisServerUri, sdk, serverVersion); - commonServer = CommonServerApi(CommonServerImpl( - sdk, - cache, - storageBucket: storageBucket, - )); + commonServer = CommonServerApi( + CommonServerImpl(sdk, cache, storageBucket: storageBucket), + ); // Set cache for GitHub OAuth and add GitHub OAuth routes to our router. GitHubOAuthHandler.setCache(cache); diff --git a/pkgs/dart_services/lib/src/analysis.dart b/pkgs/dart_services/lib/src/analysis.dart index 31e2a188e..c1bacfea9 100644 --- a/pkgs/dart_services/lib/src/analysis.dart +++ b/pkgs/dart_services/lib/src/analysis.dart @@ -31,12 +31,14 @@ class Analyzer { analysisServer = AnalysisServerWrapper(sdkPath: sdk.dartSdkPath); await analysisServer.init(); - unawaited(analysisServer.onExit.then((int code) { - _logger.severe('analysis server exited, code: $code'); - if (code != 0) { - exit(code); - } - })); + unawaited( + analysisServer.onExit.then((int code) { + _logger.severe('analysis server exited, code: $code'); + if (code != 0) { + exit(code); + } + }), + ); } Future analyze(String source) async { @@ -71,20 +73,19 @@ class AnalysisServerWrapper { /// Instance to handle communication with the server. late AnalysisServer analysisServer; - AnalysisServerWrapper({ - required this.sdkPath, - String? projectPath, - }) : - // During analysis, we use the Flutter project template. - projectPath = - projectPath ?? ProjectTemplates.projectTemplates.flutterPath; + AnalysisServerWrapper({required this.sdkPath, String? projectPath}) + : // During analysis, we use the Flutter project template. + projectPath = + projectPath ?? ProjectTemplates.projectTemplates.flutterPath; String get mainPath => _getPathFromName(kMainDart); Future init() async { const serverArgs = ['--client-id=DartPad']; - _logger.info('Starting analysis server ' - '(sdk: ${path.relative(sdkPath)}, args: ${serverArgs.join(' ')})'); + _logger.info( + 'Starting analysis server ' + '(sdk: ${path.relative(sdkPath)}, args: ${serverArgs.join(' ')})', + ); analysisServer = await AnalysisServer.create( sdkPath: sdkPath, @@ -93,8 +94,11 @@ class AnalysisServerWrapper { try { analysisServer.server.onError.listen((ServerError error) { - _logger.severe('server error${error.isFatal ? ' (fatal)' : ''}', - error.message, StackTrace.fromString(error.stackTrace)); + _logger.severe( + 'server error${error.isFatal ? ' (fatal)' : ''}', + error.message, + StackTrace.fromString(error.stackTrace), + ); }); await analysisServer.server.onConnected.first; await analysisServer.server.setSubscriptions(['STATUS']); @@ -128,47 +132,51 @@ class AnalysisServerWrapper { maxResults: maxResults, ); - final suggestions = - results.suggestions.where((CompletionSuggestion suggestion) { - // Filter suggestions that would require adding an import. - return suggestion.isNotImported != true; - }).where((CompletionSuggestion suggestion) { - if (suggestion.kind != 'IMPORT') return true; - - // We do not want to enable arbitrary discovery of file system resources. - // In order to avoid returning local file paths, we only allow returning - // import kinds that are dart: or package: imports. - if (suggestion.completion.startsWith('dart:')) { - return true; - } + final suggestions = results.suggestions + .where((CompletionSuggestion suggestion) { + // Filter suggestions that would require adding an import. + return suggestion.isNotImported != true; + }) + .where((CompletionSuggestion suggestion) { + if (suggestion.kind != 'IMPORT') return true; + + // We do not want to enable arbitrary discovery of file system resources. + // In order to avoid returning local file paths, we only allow returning + // import kinds that are dart: or package: imports. + if (suggestion.completion.startsWith('dart:')) { + return true; + } + + // Filter package suggestions to allowlisted packages. + if (suggestion.completion.startsWith('package:')) { + var packageName = suggestion.completion.substring( + 'package:'.length, + ); + packageName = packageName.split('/').first; + return isSupportedPackage(packageName); + } - // Filter package suggestions to allowlisted packages. - if (suggestion.completion.startsWith('package:')) { - var packageName = suggestion.completion.substring('package:'.length); - packageName = packageName.split('/').first; - return isSupportedPackage(packageName); - } - - return false; - }); + return false; + }); return api.CompleteResponse( replacementOffset: results.replacementOffset, replacementLength: results.replacementLength, - suggestions: suggestions.map((suggestion) { - return api.CompletionSuggestion( - kind: suggestion.kind, - relevance: suggestion.relevance, - completion: suggestion.completion, - deprecated: suggestion.isDeprecated, - selectionOffset: suggestion.selectionOffset, - displayText: suggestion.displayText, - parameterNames: suggestion.parameterNames, - returnType: suggestion.returnType, - elementKind: suggestion.element?.kind, - elementParameters: suggestion.element?.parameters, - ); - }).toList(), + suggestions: + suggestions.map((suggestion) { + return api.CompletionSuggestion( + kind: suggestion.kind, + relevance: suggestion.relevance, + completion: suggestion.completion, + deprecated: suggestion.isDeprecated, + selectionOffset: suggestion.selectionOffset, + displayText: suggestion.displayText, + parameterNames: suggestion.parameterNames, + returnType: suggestion.returnType, + elementKind: suggestion.element?.kind, + elementParameters: suggestion.element?.parameters, + ); + }).toList(), ); } @@ -185,17 +193,21 @@ class AnalysisServerWrapper { // Filter any source changes that want to act on files other than main.dart. fixChanges.removeWhere( - (change) => change.edits.any((edit) => edit.file != mainFile)); + (change) => change.edits.any((edit) => edit.file != mainFile), + ); assistsChanges.removeWhere( - (change) => change.edits.any((edit) => edit.file != mainFile)); + (change) => change.edits.any((edit) => edit.file != mainFile), + ); return api.FixesResponse( - fixes: fixChanges.map((change) { - return change.toApiSourceChange(); - }).toList(), - assists: assistsChanges.map((change) { - return change.toApiSourceChange(); - }).toList(), + fixes: + fixChanges.map((change) { + return change.toApiSourceChange(); + }).toList(), + assists: + assistsChanges.map((change) { + return change.toApiSourceChange(); + }).toList(), ); } @@ -203,25 +215,32 @@ class AnalysisServerWrapper { /// current cursor location and a modified offset is returned if necessary to /// maintain the cursors original position in the formatted code. Future format(String src, int? offset) { - return _formatImpl(src, offset).then((FormatResult editResult) { - final edits = editResult.edits; + return _formatImpl(src, offset) + .then((FormatResult editResult) { + final edits = editResult.edits; - edits.sort((SourceEdit e1, SourceEdit e2) => - -1 * e1.offset.compareTo(e2.offset)); + edits.sort( + (SourceEdit e1, SourceEdit e2) => + -1 * e1.offset.compareTo(e2.offset), + ); - for (final edit in edits) { - src = src.replaceRange( - edit.offset, edit.offset + edit.length, edit.replacement); - } + for (final edit in edits) { + src = src.replaceRange( + edit.offset, + edit.offset + edit.length, + edit.replacement, + ); + } - return api.FormatResponse( - source: src, - offset: offset == null ? 0 : editResult.selectionOffset, - ); - }).catchError((dynamic error) { - _logger.fine('format error: $error'); - return api.FormatResponse(source: src, offset: offset); - }); + return api.FormatResponse( + source: src, + offset: offset == null ? 0 : editResult.selectionOffset, + ); + }) + .catchError((dynamic error) { + _logger.fine('format error: $error'); + return api.FormatResponse(source: src, offset: offset); + }); } Future dartdoc(String src, int offset) async { @@ -255,41 +274,45 @@ class AnalysisServerWrapper { // Loop over all files and collect errors. for (final sourcePath in sources.keys) { - errors - .addAll((await analysisServer.analysis.getErrors(sourcePath)).errors); + errors.addAll( + (await analysisServer.analysis.getErrors(sourcePath)).errors, + ); } - final issues = errors.map((error) { - final issue = api.AnalysisIssue( - kind: error.severity.toLowerCase(), - message: utils.normalizeFilePaths(error.message), - code: error.code.toLowerCase(), - location: api.Location( - charStart: error.location.offset, - charLength: error.location.length, - line: error.location.startLine, - column: error.location.startColumn, - ), - correction: error.correction == null - ? null - : utils.normalizeFilePaths(error.correction!), - url: error.url, - contextMessages: error.contextMessages?.map((m) { - return api.DiagnosticMessage( - message: utils.normalizeFilePaths(m.message), + final issues = + errors.map((error) { + final issue = api.AnalysisIssue( + kind: error.severity.toLowerCase(), + message: utils.normalizeFilePaths(error.message), + code: error.code.toLowerCase(), location: api.Location( - charStart: m.location.offset, - charLength: m.location.length, - line: m.location.startLine, - column: m.location.startColumn, + charStart: error.location.offset, + charLength: error.location.length, + line: error.location.startLine, + column: error.location.startColumn, ), + correction: + error.correction == null + ? null + : utils.normalizeFilePaths(error.correction!), + url: error.url, + contextMessages: + error.contextMessages?.map((m) { + return api.DiagnosticMessage( + message: utils.normalizeFilePaths(m.message), + location: api.Location( + charStart: m.location.offset, + charLength: m.location.length, + line: m.location.startLine, + column: m.location.startColumn, + ), + ); + }).toList(), + hasFix: error.hasFix, ); - }).toList(), - hasFix: error.hasFix, - ); - return issue; - }).toList(); + return issue; + }).toList(); issues.sort((api.AnalysisIssue a, api.AnalysisIssue b) { // Order issues by severity. @@ -308,46 +331,57 @@ class AnalysisServerWrapper { if (import.dartImport) { final libraryName = import.packageName; if (!isSupportedCoreLibrary(libraryName)) { - importIssues.add(api.AnalysisIssue( - kind: 'error', - message: "Unsupported library on the web: 'dart:$libraryName'.", - correction: 'Try removing the import and usages of the library.', - location: import.getLocation(source), - )); + importIssues.add( + api.AnalysisIssue( + kind: 'error', + message: "Unsupported library on the web: 'dart:$libraryName'.", + correction: 'Try removing the import and usages of the library.', + location: import.getLocation(source), + ), + ); } } else if (import.packageImport) { final packageName = import.packageName; if (isFirebasePackage(packageName)) { - importIssues.add(api.AnalysisIssue( - kind: 'warning', - message: 'Firebase is no longer supported by DartPad.', - url: - 'https://github.com/dart-lang/dart-pad/wiki/Package-and-plugin-support#deprecated-firebase-packages', - location: import.getLocation(source), - )); + importIssues.add( + api.AnalysisIssue( + kind: 'warning', + message: 'Firebase is no longer supported by DartPad.', + url: + 'https://github.com/dart-lang/dart-pad/wiki/Package-and-plugin-support#deprecated-firebase-packages', + location: import.getLocation(source), + ), + ); } else if (isDeprecatedPackage(packageName)) { - importIssues.add(api.AnalysisIssue( - kind: 'warning', - message: "Deprecated package: 'package:$packageName'.", - correction: 'Try removing the import and usages of the package.', - url: 'https://github.com/dart-lang/dart-pad/wiki/' - 'Package-and-plugin-support#deprecated-packages', - location: import.getLocation(source), - )); + importIssues.add( + api.AnalysisIssue( + kind: 'warning', + message: "Deprecated package: 'package:$packageName'.", + correction: 'Try removing the import and usages of the package.', + url: + 'https://github.com/dart-lang/dart-pad/wiki/' + 'Package-and-plugin-support#deprecated-packages', + location: import.getLocation(source), + ), + ); } else if (!isSupportedPackage(packageName)) { - importIssues.add(api.AnalysisIssue( - kind: 'warning', - message: "Unsupported package: 'package:$packageName'.", - location: import.getLocation(source), - )); + importIssues.add( + api.AnalysisIssue( + kind: 'warning', + message: "Unsupported package: 'package:$packageName'.", + location: import.getLocation(source), + ), + ); } } else { - importIssues.add(api.AnalysisIssue( - kind: 'error', - message: 'Import type not supported.', - location: import.getLocation(source), - )); + importIssues.add( + api.AnalysisIssue( + kind: 'error', + message: 'Import type not supported.', + location: import.getLocation(source), + ), + ); } } @@ -399,7 +433,7 @@ class AnalysisServerWrapper { // Remove all the existing overlays. final contentOverlays = { for (final overlayPath in _overlayPaths) - overlayPath: RemoveContentOverlay() + overlayPath: RemoveContentOverlay(), }; // Add (or replace) new overlays for the given files. @@ -438,28 +472,31 @@ extension SourceChangeExtension on SourceChange { api.SourceChange toApiSourceChange() { return api.SourceChange( message: message, - edits: edits - .expand((fileEdit) => fileEdit.edits) - .map( - (edit) => api.SourceEdit( - offset: edit.offset, - length: edit.length, - replacement: edit.replacement, - ), - ) - .toList(), - linkedEditGroups: linkedEditGroups.map((editGroup) { - return api.LinkedEditGroup( - offsets: editGroup.positions.map((pos) => pos.offset).toList(), - length: editGroup.length, - suggestions: editGroup.suggestions.map((sug) { - return api.LinkedEditSuggestion( - value: sug.value, - kind: sug.kind, + edits: + edits + .expand((fileEdit) => fileEdit.edits) + .map( + (edit) => api.SourceEdit( + offset: edit.offset, + length: edit.length, + replacement: edit.replacement, + ), + ) + .toList(), + linkedEditGroups: + linkedEditGroups.map((editGroup) { + return api.LinkedEditGroup( + offsets: editGroup.positions.map((pos) => pos.offset).toList(), + length: editGroup.length, + suggestions: + editGroup.suggestions.map((sug) { + return api.LinkedEditSuggestion( + value: sug.value, + kind: sug.kind, + ); + }).toList(), ); }).toList(), - ); - }).toList(), selectionOffset: selection?.offset, ); } diff --git a/pkgs/dart_services/lib/src/caching.dart b/pkgs/dart_services/lib/src/caching.dart index 42ffe2dd1..9642c9f84 100644 --- a/pkgs/dart_services/lib/src/caching.dart +++ b/pkgs/dart_services/lib/src/caching.dart @@ -43,7 +43,7 @@ class RedisCache implements ServerCache { static const Duration cacheOperationTimeout = Duration(milliseconds: 10000); RedisCache(String redisUriString, this._sdk, this.serverVersion) - : redisUri = Uri.parse(redisUriString) { + : redisUri = Uri.parse(redisUriString) { _reconnect(); } @@ -116,21 +116,25 @@ class RedisCache implements ServerCache { _setUpConnection(newConnection); // If the client disconnects, discard the client and try to connect again. - newConnection.outputSink.done.then((_) { - _resetConnection(); - log.warning('$_logPrefix: connection terminated, reconnecting'); - _reconnect(); - }).catchError((dynamic e) { - _resetConnection(); - log.warning( - '$_logPrefix: connection terminated with error $e, reconnecting'); - _reconnect(); - }); + newConnection.outputSink.done + .then((_) { + _resetConnection(); + log.warning('$_logPrefix: connection terminated, reconnecting'); + _reconnect(); + }) + .catchError((dynamic e) { + _resetConnection(); + log.warning( + '$_logPrefix: connection terminated with error $e, reconnecting', + ); + _reconnect(); + }); }) .timeout(const Duration(milliseconds: _connectionRetryMaxMs)) .catchError((_) { log.severe( - '$_logPrefix: Unable to connect to redis server, reconnecting in ${nextRetryMs}ms ...'); + '$_logPrefix: Unable to connect to redis server, reconnecting in ${nextRetryMs}ms ...', + ); Future.delayed(Duration(milliseconds: nextRetryMs)).then((_) { _reconnect(nextRetryMs); }); @@ -159,12 +163,18 @@ class RedisCache implements ServerCache { } else { final commands = RespCommandsTier2(redisClient!); try { - value = await commands.get(key).timeout(cacheOperationTimeout, - onTimeout: () async { - log.warning('$_logPrefix: timeout on get operation for key $key'); - await _connection?.close(); - return null; - }); + value = await commands + .get(key) + .timeout( + cacheOperationTimeout, + onTimeout: () async { + log.warning( + '$_logPrefix: timeout on get operation for key $key', + ); + await _connection?.close(); + return null; + }, + ); } catch (e) { log.warning('$_logPrefix: error on get operation for key $key: $e'); } @@ -182,12 +192,18 @@ class RedisCache implements ServerCache { final commands = RespCommandsTier2(redisClient!); try { - await commands.del([key]).timeout(cacheOperationTimeout, - onTimeout: () async { - log.warning('$_logPrefix: timeout on remove operation for key $key'); - await _connection?.close(); - return 0; // 0 keys deleted - }); + await commands + .del([key]) + .timeout( + cacheOperationTimeout, + onTimeout: () async { + log.warning( + '$_logPrefix: timeout on remove operation for key $key', + ); + await _connection?.close(); + return 0; // 0 keys deleted + }, + ); } catch (e) { log.warning('$_logPrefix: error on remove operation for key $key: $e'); } @@ -208,10 +224,13 @@ class RedisCache implements ServerCache { if (expiration != null) { await commands.pexpire(key, expiration); } - }).timeout(cacheOperationTimeout, onTimeout: () { - log.warning('$_logPrefix: timeout on set operation for key $key'); - _connection?.close(); - }); + }).timeout( + cacheOperationTimeout, + onTimeout: () { + log.warning('$_logPrefix: timeout on set operation for key $key'); + _connection?.close(); + }, + ); } catch (e) { log.warning('$_logPrefix: error on set operation for key $key: $e'); } diff --git a/pkgs/dart_services/lib/src/common_server.dart b/pkgs/dart_services/lib/src/common_server.dart index 218d62a40..d7c086448 100644 --- a/pkgs/dart_services/lib/src/common_server.dart +++ b/pkgs/dart_services/lib/src/common_server.dart @@ -81,7 +81,9 @@ class CommonServerApi { router.post(r'/api//compileDDC', handleCompileDDC); router.post(r'/api//compileNewDDC', handleCompileNewDDC); router.post( - r'/api//compileNewDDCReload', handleCompileNewDDCReload); + r'/api//compileNewDDCReload', + handleCompileNewDDCReload, + ); router.post(r'/api//complete', handleComplete); router.post(r'/api//fixes', handleFixes); router.post(r'/api//format', handleFormat); @@ -108,8 +110,9 @@ class CommonServerApi { Future handleAnalyze(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); final result = await serialize(() { return impl.analyzer.analyze(sourceRequest.source); @@ -121,8 +124,9 @@ class CommonServerApi { Future handleCompile(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); final results = await serialize(() { return impl.compiler.compile(sourceRequest.source); @@ -136,14 +140,15 @@ class CommonServerApi { } Future _handleCompileDDC( - Request request, - String apiVersion, - Future Function(api.CompileRequest) - compile) async { + Request request, + String apiVersion, + Future Function(api.CompileRequest) compile, + ) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final compileRequest = - api.CompileRequest.fromJson(await request.readAsJson()); + final compileRequest = api.CompileRequest.fromJson( + await request.readAsJson(), + ); final results = await serialize(() { return compile(compileRequest); @@ -154,44 +159,59 @@ class CommonServerApi { if (modulesBaseUrl != null && modulesBaseUrl.isEmpty) { modulesBaseUrl = null; } - return ok(api.CompileDDCResponse( - result: results.compiledJS!, - deltaDill: results.deltaDill, - modulesBaseUrl: modulesBaseUrl, - ).toJson()); + return ok( + api.CompileDDCResponse( + result: results.compiledJS!, + deltaDill: results.deltaDill, + modulesBaseUrl: modulesBaseUrl, + ).toJson(), + ); } else { return failure(results.problems.map((p) => p.message).join('\n')); } } Future handleCompileDDC(Request request, String apiVersion) async { - return await _handleCompileDDC(request, apiVersion, - (request) => impl.compiler.compileDDC(request.source)); + return await _handleCompileDDC( + request, + apiVersion, + (request) => impl.compiler.compileDDC(request.source), + ); } Future handleCompileNewDDC( - Request request, String apiVersion) async { - return await _handleCompileDDC(request, apiVersion, - (request) => impl.compiler.compileNewDDC(request.source)); + Request request, + String apiVersion, + ) async { + return await _handleCompileDDC( + request, + apiVersion, + (request) => impl.compiler.compileNewDDC(request.source), + ); } Future handleCompileNewDDCReload( - Request request, String apiVersion) async { + Request request, + String apiVersion, + ) async { return await _handleCompileDDC( - request, - apiVersion, - (request) => impl.compiler - .compileNewDDCReload(request.source, request.deltaDill!)); + request, + apiVersion, + (request) => + impl.compiler.compileNewDDCReload(request.source, request.deltaDill!), + ); } Future handleComplete(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); - final result = await serialize(() => - impl.analyzer.complete(sourceRequest.source, sourceRequest.offset!)); + final result = await serialize( + () => impl.analyzer.complete(sourceRequest.source, sourceRequest.offset!), + ); return ok(result.toJson()); } @@ -199,11 +219,13 @@ class CommonServerApi { Future handleFixes(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); final result = await serialize( - () => impl.analyzer.fixes(sourceRequest.source, sourceRequest.offset!)); + () => impl.analyzer.fixes(sourceRequest.source, sourceRequest.offset!), + ); return ok(result.toJson()); } @@ -211,14 +233,12 @@ class CommonServerApi { Future handleFormat(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); final result = await serialize(() { - return impl.analyzer.format( - sourceRequest.source, - sourceRequest.offset, - ); + return impl.analyzer.format(sourceRequest.source, sourceRequest.offset); }); return ok(result.toJson()); @@ -227,14 +247,12 @@ class CommonServerApi { Future handleDocument(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final sourceRequest = api.SourceRequest.fromJson( + await request.readAsJson(), + ); final result = await serialize(() { - return impl.analyzer.dartdoc( - sourceRequest.source, - sourceRequest.offset!, - ); + return impl.analyzer.dartdoc(sourceRequest.source, sourceRequest.offset!); }); return ok(result.toJson()); @@ -256,16 +274,18 @@ class CommonServerApi { ); if (response.statusCode == 302) { - return ok(api.OpenInIdxResponse(idxUrl: response.headers['location']!) - .toJson()); + return ok( + api.OpenInIdxResponse(idxUrl: response.headers['location']!).toJson(), + ); } else { return Response.internalServerError( - body: - 'Failed to read response from IDX server. Response: $response'); + body: 'Failed to read response from IDX server. Response: $response', + ); } } catch (error) { return Response.internalServerError( - body: 'Failed to read response from IDX server. Error: $error'); + body: 'Failed to read response from IDX server. Error: $error', + ); } } @@ -273,8 +293,9 @@ class CommonServerApi { Future suggestFix(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final suggestFixRequest = - api.SuggestFixRequest.fromJson(await request.readAsJson()); + final suggestFixRequest = api.SuggestFixRequest.fromJson( + await request.readAsJson(), + ); return _streamResponse( 'suggestFix', @@ -292,8 +313,9 @@ class CommonServerApi { Future generateCode(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final generateCodeRequest = - api.GenerateCodeRequest.fromJson(await request.readAsJson()); + final generateCodeRequest = api.GenerateCodeRequest.fromJson( + await request.readAsJson(), + ); return _streamResponse( 'generateCode', @@ -309,8 +331,9 @@ class CommonServerApi { Future updateCode(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final updateCodeRequest = - api.UpdateCodeRequest.fromJson(await request.readAsJson()); + final updateCodeRequest = api.UpdateCodeRequest.fromJson( + await request.readAsJson(), + ); return _streamResponse( 'updateCode', @@ -355,9 +378,7 @@ class CommonServerApi { errorMessage = 'Failed to process $action request. Error: $e'; } - return Response.internalServerError( - body: errorMessage, - ); + return Response.internalServerError(body: errorMessage); } } @@ -378,10 +399,9 @@ class CommonServerApi { } Future serialize(Future Function() fn) { - return scheduler.schedule(ClosureTask( - fn, - timeoutDuration: const Duration(minutes: 5), - )); + return scheduler.schedule( + ClosureTask(fn, timeoutDuration: const Duration(minutes: 5)), + ); } api.VersionResponse version() { @@ -436,12 +456,14 @@ extension RequestExtension on Request { } Middleware createCustomCorsHeadersMiddleware() { - return shelf_cors.createCorsHeadersMiddleware(corsHeaders: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': - 'Origin, X-Requested-With, Content-Type, Accept, x-goog-api-client' - }); + return shelf_cors.createCorsHeadersMiddleware( + corsHeaders: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': + 'Origin, X-Requested-With, Content-Type, Accept, x-goog-api-client', + }, + ); } Middleware logRequestsToLogger(Logger log) { @@ -449,18 +471,21 @@ Middleware logRequestsToLogger(Logger log) { return (request) { final watch = Stopwatch()..start(); - return Future.sync(() => innerHandler(request)).then((response) { - log.info(_formatMessage(request, watch.elapsed, response: response)); + return Future.sync(() => innerHandler(request)).then( + (response) { + log.info(_formatMessage(request, watch.elapsed, response: response)); - return response; - }, onError: (Object error, StackTrace stackTrace) { - if (error is HijackException) throw error; + return response; + }, + onError: (Object error, StackTrace stackTrace) { + if (error is HijackException) throw error; - log.info(_formatMessage(request, watch.elapsed, error: error)); + log.info(_formatMessage(request, watch.elapsed, error: error)); - // ignore: only_throw_errors - throw error; - }); + // ignore: only_throw_errors + throw error; + }, + ); }; }; } @@ -480,7 +505,8 @@ String _formatMessage( final ms = elapsedTime.inMilliseconds; final query = requestedUri.query == '' ? '' : '?${requestedUri.query}'; - var message = '${ms.toString().padLeft(5)}ms ${size.toString().padLeft(4)}k ' + var message = + '${ms.toString().padLeft(5)}ms ${size.toString().padLeft(4)}k ' '$statusCode $method ${requestedUri.path}$query'; if (error != null) { message = '$message [$error]'; diff --git a/pkgs/dart_services/lib/src/compiling.dart b/pkgs/dart_services/lib/src/compiling.dart index 24fe69da1..f4ce19074 100644 --- a/pkgs/dart_services/lib/src/compiling.dart +++ b/pkgs/dart_services/lib/src/compiling.dart @@ -27,20 +27,23 @@ class Compiler { final ProjectTemplates _projectTemplates; - Compiler( - Sdk sdk, { - required String storageBucket, - }) : this._(sdk, path.join(sdk.dartSdkPath, 'bin', 'dart'), storageBucket); + Compiler(Sdk sdk, {required String storageBucket}) + : this._(sdk, path.join(sdk.dartSdkPath, 'bin', 'dart'), storageBucket); Compiler._(this._sdk, this._dartPath, this._storageBucket) - : _ddcDriver = BazelWorkerDriver( - () => Process.start(_dartPath, [ - path.join(_sdk.dartSdkPath, 'bin', 'snapshots', - 'dartdevc.dart.snapshot'), - '--persistent_worker' - ]), - maxWorkers: 1), - _projectTemplates = ProjectTemplates.projectTemplates; + : _ddcDriver = BazelWorkerDriver( + () => Process.start(_dartPath, [ + path.join( + _sdk.dartSdkPath, + 'bin', + 'snapshots', + 'dartdevc.dart.snapshot', + ), + '--persistent_worker', + ]), + maxWorkers: 1, + ), + _projectTemplates = ProjectTemplates.projectTemplates; /// Compile the given string and return the resulting [CompilationResults]. Future compile( @@ -76,13 +79,18 @@ class Compiler { _logger.fine('About to exec: $_dartPath ${arguments.join(' ')}'); - final result = - await Process.run(_dartPath, arguments, workingDirectory: temp.path); + final result = await Process.run( + _dartPath, + arguments, + workingDirectory: temp.path, + ); if (result.exitCode != 0) { - final results = CompilationResults(problems: [ - CompilationProblem._(result.stdout as String), - ]); + final results = CompilationResults( + problems: [ + CompilationProblem._(result.stdout as String), + ], + ); return results; } else { String? sourceMap; @@ -105,8 +113,11 @@ class Compiler { } /// Compile the given string and return the resulting [DDCCompilationResults]. - Future _compileDDC(String source, - {String? deltaDill, required bool useNew}) async { + Future _compileDDC( + String source, { + String? deltaDill, + required bool useNew, + }) async { final imports = getAllImportsFor(source); final temp = Directory.systemTemp.createTempSync('dartpad'); @@ -155,10 +166,7 @@ class Compiler { '--reload-delta-kernel=$newDeltaKernelPath', if (oldDillPath != null) '--reload-last-accepted-kernel=$oldDillPath', ], - if (!useNew) ...[ - '--modules=amd', - '--module-name=dartpad_main', - ], + if (!useNew) ...['--modules=amd', '--module-name=dartpad_main'], '--no-summarize', if (usingFlutter) ...[ '-s', @@ -176,8 +184,9 @@ class Compiler { _logger.fine('About to exec dartdevc worker: ${arguments.join(' ')}"'); - final response = - await _ddcDriver.doWork(WorkRequest(arguments: arguments)); + final response = await _ddcDriver.doWork( + WorkRequest(arguments: arguments), + ); if (response.exitCode != 0) { return DDCCompilationResults.failed([ CompilationProblem._(_rewritePaths(response.output)), @@ -195,15 +204,18 @@ class Compiler { // adding the code to a script tag in an iframe rather than loading it // as an individual file from baseURL. As a workaround, this replace // statement injects a name into the module definition. - compiledJs = - compiledJs.replaceFirst('define([', "define('dartpad_main', ["); + compiledJs = compiledJs.replaceFirst( + 'define([', + "define('dartpad_main', [", + ); } final results = DDCCompilationResults( compiledJS: compiledJs, deltaDill: useNew ? base64Encode(newDeltaDill.readAsBytesSync()) : null, - modulesBaseUrl: 'https://storage.googleapis.com/$_storageBucket' + modulesBaseUrl: + 'https://storage.googleapis.com/$_storageBucket' '/${_sdk.dartVersion}/', ); return results; @@ -226,7 +238,9 @@ class Compiler { } Future compileNewDDCReload( - String source, String deltaDill) async { + String source, + String deltaDill, + ) async { return await _compileDDC(source, deltaDill: deltaDill, useNew: true); } @@ -253,9 +267,10 @@ class CompilationResults { bool get success => problems.isEmpty; @override - String toString() => success - ? 'CompilationResults: Success' - : 'Compilation errors: ${problems.join('\n')}'; + String toString() => + success + ? 'CompilationResults: Success' + : 'Compilation errors: ${problems.join('\n')}'; } /// The result of a DDC compile. @@ -266,12 +281,12 @@ class DDCCompilationResults { final List problems; DDCCompilationResults({this.compiledJS, this.deltaDill, this.modulesBaseUrl}) - : problems = const []; + : problems = const []; const DDCCompilationResults.failed(this.problems) - : compiledJS = null, - deltaDill = null, - modulesBaseUrl = null; + : compiledJS = null, + deltaDill = null, + modulesBaseUrl = null; bool get hasOutput => compiledJS != null && compiledJS!.isNotEmpty; @@ -279,9 +294,10 @@ class DDCCompilationResults { bool get success => problems.isEmpty; @override - String toString() => success - ? 'CompilationResults: Success' - : 'Compilation errors: ${problems.join('\n')}'; + String toString() => + success + ? 'CompilationResults: Success' + : 'Compilation errors: ${problems.join('\n')}'; } /// An issue associated with [CompilationResults]. @@ -339,19 +355,21 @@ bool _doNothing(String from, String to) { String _rewritePaths(String output) { final lines = output.split('\n'); - return lines.map((line) { - const token1 = 'lib/bootstrap.dart:'; - var index = line.indexOf(token1); - if (index != -1) { - return 'main.dart:${line.substring(index + token1.length)}'; - } + return lines + .map((line) { + const token1 = 'lib/bootstrap.dart:'; + var index = line.indexOf(token1); + if (index != -1) { + return 'main.dart:${line.substring(index + token1.length)}'; + } - const token2 = 'lib/main.dart:'; - index = line.indexOf(token2); - if (index != -1) { - return 'main.dart:${line.substring(index + token2.length)}'; - } + const token2 = 'lib/main.dart:'; + index = line.indexOf(token2); + if (index != -1) { + return 'main.dart:${line.substring(index + token2.length)}'; + } - return line; - }).join('\n'); + return line; + }) + .join('\n'); } diff --git a/pkgs/dart_services/lib/src/generative_ai.dart b/pkgs/dart_services/lib/src/generative_ai.dart index 71a404c85..f2c487231 100644 --- a/pkgs/dart_services/lib/src/generative_ai.dart +++ b/pkgs/dart_services/lib/src/generative_ai.dart @@ -31,37 +31,33 @@ class GenerativeAI { bool get _canGenAI => _geminiApiKey != null; - late final _flutterFixModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.flutter, - ''' + late final _flutterFixModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.flutter, ''' You will be given an error message in provided Flutter source code along with an optional line and column number where the error appears. Please fix the code and return it in it's entirety. The response should be the same program as the input with the error fixed. -''', - ), - ) - : null; - - late final _dartFixModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.dart, - ''' +'''), + ) + : null; + + late final _dartFixModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.dart, ''' You will be given an error message in provided Dart source code along with an optional line and column number where the error appears. Please fix the code and return it in it's entirety. The response should be the same program as the input with the error fixed. -''', - ), - ) - : null; +'''), + ) + : null; Stream suggestFix({ required AppType appType, @@ -90,31 +86,27 @@ $source yield* cleanCode(_textOnly(stream)); } - late final _newFlutterCodeModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.flutter, - ''' + late final _newFlutterCodeModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.flutter, ''' Generate a Flutter program that satisfies the provided description. -''', - ), - ) - : null; - - late final _newDartCodeModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.dart, - ''' +'''), + ) + : null; + + late final _newDartCodeModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.dart, ''' Generate a Dart program that satisfies the provided description. -''', - ), - ) - : null; +'''), + ) + : null; Stream generateCode({ required AppType appType, @@ -138,35 +130,31 @@ Generate a Dart program that satisfies the provided description. yield* cleanCode(_textOnly(stream)); } - late final _updateFlutterCodeModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.flutter, - ''' + late final _updateFlutterCodeModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.flutter, ''' You will be given an existing Flutter program and a description of a change to be made to it. Generate an updated Flutter program that satisfies the description. -''', - ), - ) - : null; - - late final _updateDartCodeModel = _canGenAI - ? GenerativeModel( - apiKey: _geminiApiKey!, - model: _geminiModel, - systemInstruction: _systemInstructions( - AppType.dart, - ''' +'''), + ) + : null; + + late final _updateDartCodeModel = + _canGenAI + ? GenerativeModel( + apiKey: _geminiApiKey!, + model: _geminiModel, + systemInstruction: _systemInstructions(AppType.dart, ''' You will be given an existing Dart program and a description of a change to be made to it. Generate an updated Dart program that satisfies the description. -''', - ), - ) - : null; +'''), + ) + : null; Stream updateCode({ required AppType appType, @@ -271,9 +259,10 @@ $prompt if (cachedList.isEmpty) { final versions = getPackageVersions(); for (final MapEntry(key: name, value: version) in versions.entries) { - final isSupported = appType == AppType.flutter - ? isSupportedPackage(name) - : isSupportedDartPackage(name); + final isSupported = + appType == AppType.flutter + ? isSupportedPackage(name) + : isSupportedDartPackage(name); if (isSupported) cachedList.add('$name: $version'); } } diff --git a/pkgs/dart_services/lib/src/oauth_handler.dart b/pkgs/dart_services/lib/src/oauth_handler.dart index ef0fd85ec..d703fb353 100644 --- a/pkgs/dart_services/lib/src/oauth_handler.dart +++ b/pkgs/dart_services/lib/src/oauth_handler.dart @@ -60,8 +60,10 @@ class GitHubOAuthHandler { if (!initializationEndedInErrorState) { // Add our routes to the router. _logger.fine('Adding GitHub OAuth routes to passed router.'); - router.get('/$entryPointGitHubOAuthInitiate/', - _initiateHandler); + router.get( + '/$entryPointGitHubOAuthInitiate/', + _initiateHandler, + ); router.get('/$entryPointGitHubReturnAuthorize', _returnAuthorizeHandler); } else { _logger.fine('''Attempt to add GitHub OAuth routes to router FAILED @@ -89,31 +91,36 @@ because initialization of GitHubOAuthHandler failed earlier.'''); final clientId = _stripQuotes(Platform.environment['PK_GITHUB_OAUTH_CLIENT_ID']) ?? - 'MissingClientIdEnvironmentalVariable'; + 'MissingClientIdEnvironmentalVariable'; final clientSecret = _stripQuotes(Platform.environment['PK_GITHUB_OAUTH_CLIENT_SECRET']) ?? - 'MissingClientSecretEnvironmentalVariable'; + 'MissingClientSecretEnvironmentalVariable'; var authReturnUrl = _stripQuotes(Platform.environment['K_GITHUB_OAUTH_AUTH_RETURN_URL']) ?? - ''; - var returnToAppUrl = _stripQuotes( - Platform.environment['K_GITHUB_OAUTH_RETURN_TO_APP_URL']) ?? + ''; + var returnToAppUrl = + _stripQuotes( + Platform.environment['K_GITHUB_OAUTH_RETURN_TO_APP_URL'], + ) ?? ''; var missingEnvVariables = false; if (clientId == 'MissingClientIdEnvironmentalVariable') { _logger.severe( - 'PK_GITHUB_OAUTH_CLIENT_ID environmental variable not set! This is REQUIRED.'); + 'PK_GITHUB_OAUTH_CLIENT_ID environmental variable not set! This is REQUIRED.', + ); missingEnvVariables = true; } if (clientSecret == 'MissingClientSecretEnvironmentalVariable') { _logger.severe( - 'PK_GITHUB_OAUTH_CLIENT_SECRET environmental variable not set! This is REQUIRED.'); + 'PK_GITHUB_OAUTH_CLIENT_SECRET environmental variable not set! This is REQUIRED.', + ); missingEnvVariables = true; } if (missingEnvVariables) { _logger.severe( - 'GitHub OAuth Handler DISABLED - Ensure all required environmental variables are set and re-run.'); + 'GitHub OAuth Handler DISABLED - Ensure all required environmental variables are set and re-run.', + ); initializationEndedInErrorState = true; return false; } @@ -129,13 +136,15 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' // This would be the locally running dart-services server. authReturnUrl = 'http://localhost:8080/$entryPointGitHubReturnAuthorize'; _logger.fine( - 'K_GITHUB_OAUTH_AUTH_RETURN_URL environmental variable not set - defaulting to "$authReturnUrl"'); + 'K_GITHUB_OAUTH_AUTH_RETURN_URL environmental variable not set - defaulting to "$authReturnUrl"', + ); } if (returnToAppUrl.isEmpty) { // This would be the locally running dart-pad server. returnToAppUrl = 'http://localhost:8000/index.html'; _logger.fine( - 'K_GITHUB_OAUTH_RETURN_TO_APP_URL environmental variable not set - defaulting to "$returnToAppUrl"'); + 'K_GITHUB_OAUTH_RETURN_TO_APP_URL environmental variable not set - defaulting to "$returnToAppUrl"', + ); } return init(clientId, clientSecret, authReturnUrl, returnToAppUrl); } @@ -144,8 +153,12 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' /// static class variables. /// All required parameters are passed directly to this init() routine. /// Returns true if initialization was successful. - static Future init(String clientId, String clientSecret, - String authReturnUrl, String returnToAppUrl) async { + static Future init( + String clientId, + String clientSecret, + String authReturnUrl, + String returnToAppUrl, + ) async { _clientId = clientId; _clientSecret = clientSecret; _authReturnUrl = authReturnUrl; @@ -162,17 +175,20 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' } if (_authReturnUrl.isEmpty) { _logger.severe( - 'GitHubOAuthHandler no authorization return url passed to init().'); + 'GitHubOAuthHandler no authorization return url passed to init().', + ); missingParameters = true; } if (_returnToAppUrl.isEmpty) { - _logger - .severe('GitHubOAuthHandler no return ti app url passed to init().'); + _logger.severe( + 'GitHubOAuthHandler no return ti app url passed to init().', + ); missingParameters = true; } if (missingParameters) { _logger.severe( - 'GitHub OAuth Handler DISABLED - Ensure all required parameters not passed to init().'); + 'GitHub OAuth Handler DISABLED - Ensure all required parameters not passed to init().', + ); initializationEndedInErrorState = true; return false; } @@ -190,7 +206,9 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' /// The calling app will need to use the originally sent random token /// to decrypt the returned GitHub authorization token. static Future _initiateHandler( - Request request, String randomState) async { + Request request, + String randomState, + ) async { // See if we have anything stored for this random state. var timestampStr = await _cache.get(randomState); var newRequest = false; @@ -206,8 +224,11 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' // Store this state/timestamp pair within the cache so // we can later verify state on a return from GitHub. - await _cache.set(randomState, timestampStr, - expiration: tenMinuteExpiration); + await _cache.set( + randomState, + timestampStr, + expiration: tenMinuteExpiration, + ); /* Incoming Random String from DartPad. @@ -298,42 +319,46 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' final bodydata = json.encode(map); await client - .post(Uri.parse(githubExchangeCodeUri), - headers: { - 'Accept': 'application/vnd.github.v3+json', - 'Content-Type': 'application/json', - }, - body: bodydata) + .post( + Uri.parse(githubExchangeCodeUri), + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json', + }, + body: bodydata, + ) .then((http.Response postResponse) { - late String accessToken, scope; - if (postResponse.statusCode >= 200 && - postResponse.statusCode <= 299) { - final retObj = - jsonDecode(postResponse.body) as Map; - - accessToken = retObj['access_token'] as String; - scope = retObj['scope'] as String; - - tokenAquired = true; - - // We can delete this record because we are done. - _cache.remove(state); - - // Encrypt the auth token using the original random state. - final encrBase64AuthToken = - _encryptAndBase64EncodeAuthToken(accessToken, state); - // Build URL to redirect back to the app. - backToAppUrl += '?gh=$encrBase64AuthToken&scope=$scope'; - - _logger.fine('success - redirecting back to app'); - } else if (postResponse.statusCode == 404) { - throw Exception('contentNotFound'); - } else if (postResponse.statusCode == 403) { - throw Exception('rateLimitExceeded'); - } else if (postResponse.statusCode != 200) { - throw Exception('unknown'); - } - }); + late String accessToken, scope; + if (postResponse.statusCode >= 200 && + postResponse.statusCode <= 299) { + final retObj = + jsonDecode(postResponse.body) as Map; + + accessToken = retObj['access_token'] as String; + scope = retObj['scope'] as String; + + tokenAquired = true; + + // We can delete this record because we are done. + _cache.remove(state); + + // Encrypt the auth token using the original random state. + final encrBase64AuthToken = _encryptAndBase64EncodeAuthToken( + accessToken, + state, + ); + // Build URL to redirect back to the app. + backToAppUrl += '?gh=$encrBase64AuthToken&scope=$scope'; + + _logger.fine('success - redirecting back to app'); + } else if (postResponse.statusCode == 404) { + throw Exception('contentNotFound'); + } else if (postResponse.statusCode == 403) { + throw Exception('rateLimitExceeded'); + } else if (postResponse.statusCode != 200) { + throw Exception('unknown'); + } + }); } if (!validCallback || !tokenAquired) { @@ -361,7 +386,9 @@ Enviroment K_GITHUB_OAUTH_RETURN_TO_APP_URL=$returnToAppUrl' /// The symetric decrypting routine is used client side in Dart-Pad t /// decrypt the received token. static String _encryptAndBase64EncodeAuthToken( - String ghAuthToken, String randomStateWeWereSent) { + String ghAuthToken, + String randomStateWeWereSent, + ) { if (randomStateWeWereSent.isEmpty) { return 'ERROR-no stored initial state'; } diff --git a/pkgs/dart_services/lib/src/project_creator.dart b/pkgs/dart_services/lib/src/project_creator.dart index aa5234cfc..60e62e101 100644 --- a/pkgs/dart_services/lib/src/project_creator.dart +++ b/pkgs/dart_services/lib/src/project_creator.dart @@ -30,9 +30,9 @@ class ProjectCreator { required String dartLanguageVersion, required File dependenciesFile, required LogFunction log, - }) : _dartLanguageVersion = dartLanguageVersion, - _dependenciesFile = dependenciesFile, - _log = log; + }) : _dartLanguageVersion = dartLanguageVersion, + _dependenciesFile = dependenciesFile, + _log = log; /// Builds a basic Dart project template directory, complete with `pubspec.yaml` /// and `analysis_options.yaml`. @@ -41,20 +41,22 @@ class ProjectCreator { final projectDirectory = Directory(projectPath); await projectDirectory.create(recursive: true); final dependencies = _dependencyVersions(supportedBasicDartPackages); - File(path.join(projectPath, 'pubspec.yaml')) - .writeAsStringSync(createPubspec( - includeFlutterWeb: false, - dartLanguageVersion: _dartLanguageVersion, - dependencies: dependencies, - )); + File(path.join(projectPath, 'pubspec.yaml')).writeAsStringSync( + createPubspec( + includeFlutterWeb: false, + dartLanguageVersion: _dartLanguageVersion, + dependencies: dependencies, + ), + ); final exitCode = await runFlutterPubGet(_sdk, projectPath, log: _log); if (exitCode != 0) { throw StateError('pub get failed ($exitCode)'); } - File(path.join(projectPath, 'analysis_options.yaml')) - .writeAsStringSync(_createAnalysisOptionsContents()); + File( + path.join(projectPath, 'analysis_options.yaml'), + ).writeAsStringSync(_createAnalysisOptionsContents()); } /// Builds a Flutter project template directory, complete with `pubspec.yaml`, @@ -70,12 +72,13 @@ class ProjectCreator { ...supportedBasicDartPackages, ...supportedFlutterPackages, }); - File(path.join(projectPath, 'pubspec.yaml')) - .writeAsStringSync(createPubspec( - includeFlutterWeb: true, - dartLanguageVersion: _dartLanguageVersion, - dependencies: dependencies, - )); + File(path.join(projectPath, 'pubspec.yaml')).writeAsStringSync( + createPubspec( + includeFlutterWeb: true, + dartLanguageVersion: _dartLanguageVersion, + dependencies: dependencies, + ), + ); final exitCode = await runFlutterPubGet(_sdk, projectPath, log: _log); if (exitCode != 0) { @@ -85,16 +88,24 @@ class ProjectCreator { // Working around Flutter 3.3's deprecation of generated_plugin_registrant.dart // Context: https://github.com/flutter/flutter/pull/106921 - final pluginRegistrant = File(path.join( - projectPath, '.dart_tool', 'dartpad', 'web_plugin_registrant.dart')); + final pluginRegistrant = File( + path.join( + projectPath, + '.dart_tool', + 'dartpad', + 'web_plugin_registrant.dart', + ), + ); if (pluginRegistrant.existsSync()) { Directory(path.join(projectPath, 'lib')).createSync(); pluginRegistrant.copySync( - path.join(projectPath, 'lib', 'generated_plugin_registrant.dart')); + path.join(projectPath, 'lib', 'generated_plugin_registrant.dart'), + ); } - File(path.join(projectPath, 'analysis_options.yaml')) - .writeAsStringSync(_createAnalysisOptionsContents()); + File( + path.join(projectPath, 'analysis_options.yaml'), + ).writeAsStringSync(_createAnalysisOptionsContents()); } String _createAnalysisOptionsContents() { @@ -113,8 +124,9 @@ ${_sdk.experiments.map((experiment) => ' - $experiment').join('\n')} } Map _dependencyVersions(Iterable packages) { - final allVersions = - parsePubDependenciesFile(dependenciesFile: _dependenciesFile); + final allVersions = parsePubDependenciesFile( + dependenciesFile: _dependenciesFile, + ); return { for (final package in packages) package: allVersions[package] ?? 'any', }; diff --git a/pkgs/dart_services/lib/src/project_templates.dart b/pkgs/dart_services/lib/src/project_templates.dart index 51c5c6bf5..40f121767 100644 --- a/pkgs/dart_services/lib/src/project_templates.dart +++ b/pkgs/dart_services/lib/src/project_templates.dart @@ -18,10 +18,7 @@ class ProjectTemplates { factory ProjectTemplates() { final basePath = _baseTemplateProject(); - final summaryFilePath = path.join( - 'artifacts', - 'flutter_web.dill', - ); + final summaryFilePath = path.join('artifacts', 'flutter_web.dill'); return ProjectTemplates._( dartPath: path.join(basePath, 'dart_project'), flutterPath: path.join(basePath, 'flutter_project'), diff --git a/pkgs/dart_services/lib/src/pub.dart b/pkgs/dart_services/lib/src/pub.dart index 147c176d0..43f2cbfd9 100644 --- a/pkgs/dart_services/lib/src/pub.dart +++ b/pkgs/dart_services/lib/src/pub.dart @@ -28,7 +28,8 @@ const _flutterPackages = [ /// This is expensive to calculate; they require reading from disk. /// None of them changes during execution. final Map _packageVersions = packageVersionsFromPubspecLock( - project.ProjectTemplates.projectTemplates.flutterPath); + project.ProjectTemplates.projectTemplates.flutterPath, +); /// Returns a mapping of Pub package name to package version. Map getPackageVersions() => _packageVersions; @@ -58,7 +59,8 @@ Map packageVersionsFromPubspecLock(String templatePath) { packageVersions[name] = version; } else { throw StateError( - '$name does not have a well-formatted version: $version'); + '$name does not have a well-formatted version: $version', + ); } }); diff --git a/pkgs/dart_services/lib/src/sdk.dart b/pkgs/dart_services/lib/src/sdk.dart index a0aa5cc7e..8509af455 100644 --- a/pkgs/dart_services/lib/src/sdk.dart +++ b/pkgs/dart_services/lib/src/sdk.dart @@ -69,8 +69,11 @@ final class Sdk { // looking for a 'FLUTTER_ROOT' environment variable. // /bin/cache/dart-sdk/bin/dart - final potentialFlutterSdkPath = path.dirname(path.dirname( - path.dirname(path.dirname(path.dirname(Platform.resolvedExecutable))))); + final potentialFlutterSdkPath = path.dirname( + path.dirname( + path.dirname(path.dirname(path.dirname(Platform.resolvedExecutable))), + ), + ); final String flutterSdkPath; if (_validFlutterSdk(potentialFlutterSdkPath)) { @@ -122,17 +125,21 @@ final class Sdk { // analytics disclaimer). try { - return jsonDecode(Process.runSync( - flutterToolPath, - ['--version', '--machine'], - workingDirectory: flutterSdkPath, - ).stdout.toString().trim()) as Map; + return jsonDecode( + Process.runSync(flutterToolPath, [ + '--version', + '--machine', + ], workingDirectory: flutterSdkPath).stdout.toString().trim(), + ) + as Map; } on FormatException { - return jsonDecode(Process.runSync( - flutterToolPath, - ['--version', '--machine'], - workingDirectory: flutterSdkPath, - ).stdout.toString().trim()) as Map; + return jsonDecode( + Process.runSync(flutterToolPath, [ + '--version', + '--machine', + ], workingDirectory: flutterSdkPath).stdout.toString().trim(), + ) + as Map; } } diff --git a/pkgs/dart_services/lib/src/shelf_cors.dart b/pkgs/dart_services/lib/src/shelf_cors.dart index 5314525e3..f54c720db 100644 --- a/pkgs/dart_services/lib/src/shelf_cors.dart +++ b/pkgs/dart_services/lib/src/shelf_cors.dart @@ -27,5 +27,7 @@ Middleware createCorsHeadersMiddleware({ response.change(headers: corsHeaders); return createMiddleware( - requestHandler: handleOptionsRequest, responseHandler: addCorsHeaders); + requestHandler: handleOptionsRequest, + responseHandler: addCorsHeaders, + ); } diff --git a/pkgs/dart_services/lib/src/utils.dart b/pkgs/dart_services/lib/src/utils.dart index d2403b00b..4b96e8064 100644 --- a/pkgs/dart_services/lib/src/utils.dart +++ b/pkgs/dart_services/lib/src/utils.dart @@ -52,17 +52,22 @@ Future runWithLogging( Map environment = const {}, required void Function(String) log, }) async { - log([ - '${path.basename(executable)} ${arguments.join(' ')}:', - if (workingDirectory != null) 'cwd: $workingDirectory', - if (environment.isNotEmpty) 'env: $environment', - ].join('\n ')); - - final process = await Process.start(executable, arguments, - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: true, - runInShell: Platform.isWindows); + log( + [ + '${path.basename(executable)} ${arguments.join(' ')}:', + if (workingDirectory != null) 'cwd: $workingDirectory', + if (environment.isNotEmpty) 'env: $environment', + ].join('\n '), + ); + + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: true, + runInShell: Platform.isWindows, + ); process.stdout.listen((out) => log(systemEncoding.decode(out).trimRight())); process.stderr.listen((out) => log(systemEncoding.decode(out).trimRight())); return process; diff --git a/pkgs/dart_services/pubspec.yaml b/pkgs/dart_services/pubspec.yaml index b8f0936ca..ce78087f5 100644 --- a/pkgs/dart_services/pubspec.yaml +++ b/pkgs/dart_services/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.6.1 + sdk: ^3.7.0 dependencies: analysis_server_lib: ^0.2.5 diff --git a/pkgs/dart_services/test/analysis_test.dart b/pkgs/dart_services/test/analysis_test.dart index 9ee88995b..08f4e5d92 100644 --- a/pkgs/dart_services/test/analysis_test.dart +++ b/pkgs/dart_services/test/analysis_test.dart @@ -41,17 +41,19 @@ void defineTests() { expect(issue.location.line, 2); expect(issue.location.column, 7); expect(issue.kind, 'info'); - expect(issue.message, - 'An uninitialized variable should have an explicit type annotation.'); + expect( + issue.message, + 'An uninitialized variable should have an explicit type annotation.', + ); expect(issue.code, 'prefer_typing_uninitialized_variables'); }); test('completions polluted on second request (repro #126)', () async { // https://github.com/dart-lang/dart-services/issues/126 return analysisServer.complete(completionFilterCode, 17).then((results) { - return analysisServer - .complete(completionFilterCode, 17) - .then((results) { + return analysisServer.complete(completionFilterCode, 17).then(( + results, + ) { expect(results.replacementLength, 2); expect(results.replacementOffset, 16); expect(completionsContains(results, 'print'), true); @@ -68,10 +70,13 @@ void defineTests() { final completions = results.suggestions; if (completions.isNotEmpty) { - expect(completions.every((completion) { - return completion.completion.startsWith('dart:') || - completion.completion.startsWith('package:'); - }), true); + expect( + completions.every((completion) { + return completion.completion.startsWith('dart:') || + completion.completion.startsWith('package:'); + }), + true, + ); } }); @@ -91,13 +96,15 @@ void defineTests() { final completions = results.suggestions; expect( - completions - .every((completion) => completion.completion.startsWith('dart:')), + completions.every( + (completion) => completion.completion.startsWith('dart:'), + ), true, ); expect( - completions - .any((completion) => completion.completion.startsWith('dart:')), + completions.any( + (completion) => completion.completion.startsWith('dart:'), + ), true, ); }); @@ -121,7 +128,9 @@ void defineTests() { // "Insert ';'" expect(changes.map((e) => e.message), contains(startsWith('Insert '))); expect( - changes.map((e) => e.edits.first.replacement), contains(equals(';'))); + changes.map((e) => e.edits.first.replacement), + contains(equals(';')), + ); }); test('format simple', () async { @@ -130,8 +139,10 @@ void defineTests() { }); test('format good code', () async { - final results = - await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0); + final results = await analysisServer.format( + formattedCode.replaceAll('\n', ' '), + 0, + ); expect(results.source, formattedCode); }); @@ -154,8 +165,10 @@ void defineTests() { // just after A final idx = 61; expect(completionLargeNamespaces.substring(idx - 1, idx), 'A'); - final results = - await analysisServer.complete(completionLargeNamespaces, 61); + final results = await analysisServer.complete( + completionLargeNamespaces, + 61, + ); expect(completionsContains(results, 'A'), true); expect(completionsContains(results, 'AB'), true); expect(completionsContains(results, 'ABC'), true); @@ -193,8 +206,9 @@ void defineTests() { }); test('analyze Draggable Physics sample', () async { - final results = - await analysisServer.analyze(sampleCodeFlutterDraggableCard); + final results = await analysisServer.analyze( + sampleCodeFlutterDraggableCard, + ); expect(results.issues, isEmpty); }); @@ -219,9 +233,10 @@ class HelloWorld extends StatelessWidget { expect(issue.location.line, 3); expect(issue.kind, 'error'); expect( - issue.message, - "A value of type 'int' can't be assigned to a variable of type " - "'String'."); + issue.message, + "A value of type 'int' can't be assigned to a variable of type " + "'String'.", + ); }); // https://github.com/dart-lang/dart-pad/issues/2005 @@ -246,8 +261,10 @@ class HelloWorld extends StatelessWidget { final issue = results.issues[0]; expect(issue.location.line, 4); expect(issue.kind, 'info'); - expect(issue.message, - 'An uninitialized variable should have an explicit type annotation.'); + expect( + issue.message, + 'An uninitialized variable should have an explicit type annotation.', + ); }); test('analyze counter app', () async { @@ -256,8 +273,9 @@ class HelloWorld extends StatelessWidget { }); test('analyze Draggable Physics sample', () async { - final results = - await analysisServer.analyze(sampleCodeFlutterDraggableCard); + final results = await analysisServer.analyze( + sampleCodeFlutterDraggableCard, + ); expect(results.issues, isEmpty); }); @@ -267,8 +285,9 @@ class HelloWorld extends StatelessWidget { }); test('analyze Draggable Physics sample', () async { - final results = - await analysisServer.analyze(sampleCodeFlutterDraggableCard); + final results = await analysisServer.analyze( + sampleCodeFlutterDraggableCard, + ); expect(results.issues, isEmpty); }); }); @@ -276,8 +295,9 @@ class HelloWorld extends StatelessWidget { /// Returns whether the completion [response] contains [expected]. bool completionsContains(api.CompleteResponse response, String expected) { - return response.suggestions - .any((completion) => completion.completion == expected); + return response.suggestions.any( + (completion) => completion.completion == expected, + ); } const completionCode = r''' diff --git a/pkgs/dart_services/test/caching_test.dart b/pkgs/dart_services/test/caching_test.dart index 4026f7a62..24bedaad2 100644 --- a/pkgs/dart_services/test/caching_test.dart +++ b/pkgs/dart_services/test/caching_test.dart @@ -38,14 +38,20 @@ void defineTests(bool hasRedis) { final singleStreamOnly = Lock(); Future startRedisProcessAndDrainIO(int port) async { - final newRedisProcess = - await Process.start('redis-server', ['--port', port.toString()]); - unawaited(singleStreamOnly.synchronized(() async { - await stdout.addStream(newRedisProcess.stdout); - })); - unawaited(singleStreamOnly.synchronized(() async { - await stderr.addStream(newRedisProcess.stderr); - })); + final newRedisProcess = await Process.start('redis-server', [ + '--port', + port.toString(), + ]); + unawaited( + singleStreamOnly.synchronized(() async { + await stdout.addStream(newRedisProcess.stdout); + }), + ); + unawaited( + singleStreamOnly.synchronized(() async { + await stderr.addStream(newRedisProcess.stderr); + }), + ); return newRedisProcess; } @@ -90,8 +96,11 @@ void defineTests(bool hasRedis) { test('Verify values expire', () async { await singleTestOnly.synchronized(() async { logMessages = []; - await redisCache.set('expiringkey', 'expiringValue', - expiration: const Duration(milliseconds: 1)); + await redisCache.set( + 'expiringkey', + 'expiringValue', + expiration: const Duration(milliseconds: 1), + ); await Future.delayed(const Duration(milliseconds: 100)); await expectLater(await redisCache.get('expiringkey'), isNull); expect(logMessages, isEmpty); @@ -99,140 +108,172 @@ void defineTests(bool hasRedis) { }); test( - 'Verify two caches with different versions give different results for keys', - () async { - await singleTestOnly.synchronized(() async { - logMessages = []; - await redisCache.set('differentVersionKey', 'value1'); - await redisCacheAlt.set('differentVersionKey', 'value2'); - await expectLater( - await redisCache.get('differentVersionKey'), 'value1'); - await expectLater( - await redisCacheAlt.get('differentVersionKey'), 'value2'); - expect(logMessages, isEmpty); - }); - }); + 'Verify two caches with different versions give different results for keys', + () async { + await singleTestOnly.synchronized(() async { + logMessages = []; + await redisCache.set('differentVersionKey', 'value1'); + await redisCacheAlt.set('differentVersionKey', 'value2'); + await expectLater( + await redisCache.get('differentVersionKey'), + 'value1', + ); + await expectLater( + await redisCacheAlt.get('differentVersionKey'), + 'value2', + ); + expect(logMessages, isEmpty); + }); + }, + ); test('Verify disconnected cache logs errors and returns nulls', () async { await singleTestOnly.synchronized(() async { logMessages = []; - final redisCacheBroken = - RedisCache('redis://localhost:9502', sdk, 'cversion'); + final redisCacheBroken = RedisCache( + 'redis://localhost:9502', + sdk, + 'cversion', + ); try { await redisCacheBroken.set('aKey', 'value'); await expectLater(await redisCacheBroken.get('aKey'), isNull); await redisCacheBroken.remove('aKey'); expect( - logMessages.join('\n'), - stringContainsInOrder([ - 'no cache available when setting key server:rc:cversion:dart:', - '+aKey', - 'no cache available when getting key server:rc:cversion:dart:', - '+aKey', - 'no cache available when removing key server:rc:cversion:dart:', - '+aKey', - ])); + logMessages.join('\n'), + stringContainsInOrder([ + 'no cache available when setting key server:rc:cversion:dart:', + '+aKey', + 'no cache available when getting key server:rc:cversion:dart:', + '+aKey', + 'no cache available when removing key server:rc:cversion:dart:', + '+aKey', + ]), + ); } finally { await redisCacheBroken.shutdown(); } }); }); - test('Verify cache that starts out disconnected retries and works (slow)', - () async { - await singleTestOnly.synchronized(() async { - logMessages = []; - final redisCacheRepairable = - RedisCache('redis://localhost:9503', sdk, 'cversion'); - try { - // Wait for a retry message. - while (logMessages.length < 2) { - await Future.delayed(const Duration(milliseconds: 50)); - } - expect( + test( + 'Verify cache that starts out disconnected retries and works (slow)', + () async { + await singleTestOnly.synchronized(() async { + logMessages = []; + final redisCacheRepairable = RedisCache( + 'redis://localhost:9503', + sdk, + 'cversion', + ); + try { + // Wait for a retry message. + while (logMessages.length < 2) { + await Future.delayed(const Duration(milliseconds: 50)); + } + expect( logMessages.join('\n'), stringContainsInOrder([ 'reconnecting to redis://localhost:9503...\n', 'Unable to connect to redis server, reconnecting in', - ])); + ]), + ); - // Start a redis server. - redisAltProcess = await startRedisProcessAndDrainIO(9503); + // Start a redis server. + redisAltProcess = await startRedisProcessAndDrainIO(9503); - // Wait for connection. - await redisCacheRepairable.connected; - expect(logMessages.join('\n'), contains('Connected to redis server')); - } finally { - await redisCacheRepairable.shutdown(); - } - }); - }); + // Wait for connection. + await redisCacheRepairable.connected; + expect( + logMessages.join('\n'), + contains('Connected to redis server'), + ); + } finally { + await redisCacheRepairable.shutdown(); + } + }); + }, + ); test( - 'Verify that cache that stops responding temporarily times out and can recover', - () async { - await singleTestOnly.synchronized(() async { - logMessages = []; - await redisCache.set('beforeStop', 'truth'); - redisProcess!.kill(ProcessSignal.sigstop); - // Don't fail the test before sending sigcont. - final beforeStop = await redisCache.get('beforeStop'); - await redisCache.disconnected; - redisProcess!.kill(ProcessSignal.sigcont); - expect(beforeStop, isNull); - await redisCache.connected; - await expectLater(await redisCache.get('beforeStop'), equals('truth')); - expect( + 'Verify that cache that stops responding temporarily times out and can recover', + () async { + await singleTestOnly.synchronized(() async { + logMessages = []; + await redisCache.set('beforeStop', 'truth'); + redisProcess!.kill(ProcessSignal.sigstop); + // Don't fail the test before sending sigcont. + final beforeStop = await redisCache.get('beforeStop'); + await redisCache.disconnected; + redisProcess!.kill(ProcessSignal.sigcont); + expect(beforeStop, isNull); + await redisCache.connected; + await expectLater( + await redisCache.get('beforeStop'), + equals('truth'), + ); + expect( logMessages.join('\n'), stringContainsInOrder([ 'timeout on get operation for key server:rc:aversion:dart:', '+beforeStop', '(aversion): reconnecting', '(aversion): Connected to redis server', - ])); - }); - }, onPlatform: { - 'windows': const Skip('Windows does not have sigstop/sigcont'), - }); + ]), + ); + }); + }, + onPlatform: { + 'windows': const Skip('Windows does not have sigstop/sigcont'), + }, + ); test( - 'Verify cache that starts out connected but breaks retries until reconnection (slow)', - () async { - await singleTestOnly.synchronized(() async { - logMessages = []; + 'Verify cache that starts out connected but breaks retries until reconnection (slow)', + () async { + await singleTestOnly.synchronized(() async { + logMessages = []; - redisAltProcess = await startRedisProcessAndDrainIO(9504); - final redisCacheHealing = - RedisCache('redis://localhost:9504', sdk, 'cversion'); - try { - await redisCacheHealing.connected; - await redisCacheHealing.set('missingKey', 'value'); - // Kill process out from under the cache. - redisAltProcess!.kill(); - await redisAltProcess!.exitCode; - redisAltProcess = null; - - // Try to talk to the cache and get an error. Wait for the disconnect - // to be recognized. - await expectLater(await redisCacheHealing.get('missingKey'), isNull); - await redisCacheHealing.disconnected; - - // Start the server and verify we connect appropriately. redisAltProcess = await startRedisProcessAndDrainIO(9504); - await redisCacheHealing.connected; - expect( + final redisCacheHealing = RedisCache( + 'redis://localhost:9504', + sdk, + 'cversion', + ); + try { + await redisCacheHealing.connected; + await redisCacheHealing.set('missingKey', 'value'); + // Kill process out from under the cache. + redisAltProcess!.kill(); + await redisAltProcess!.exitCode; + redisAltProcess = null; + + // Try to talk to the cache and get an error. Wait for the disconnect + // to be recognized. + await expectLater( + await redisCacheHealing.get('missingKey'), + isNull, + ); + await redisCacheHealing.disconnected; + + // Start the server and verify we connect appropriately. + redisAltProcess = await startRedisProcessAndDrainIO(9504); + await redisCacheHealing.connected; + expect( logMessages.join('\n'), stringContainsInOrder([ 'Connected to redis server', 'connection terminated with error SocketException', 'reconnecting to redis://localhost:9504', - ])); - expect(logMessages.last, contains('Connected to redis server')); - } finally { - await redisCacheHealing.shutdown(); - } - }); - }); + ]), + ); + expect(logMessages.last, contains('Connected to redis server')); + } finally { + await redisCacheHealing.shutdown(); + } + }); + }, + ); }, skip: hasRedis ? null : 'redis-server not installed'); } diff --git a/pkgs/dart_services/test/compiling_test.dart b/pkgs/dart_services/test/compiling_test.dart index 57f1a5d87..b97e2f521 100644 --- a/pkgs/dart_services/test/compiling_test.dart +++ b/pkgs/dart_services/test/compiling_test.dart @@ -32,17 +32,22 @@ void defineTests() { expect(result.sourceMap, isNull); }); - void testDDCEndpoint(String endpointName, - {required Future Function(String source) - restartEndpoint, - Future Function( - String source, String lastAcceptedDill)? - reloadEndpoint, - required bool expectNewDeltaDill, - required String compiledIndicator}) { + void testDDCEndpoint( + String endpointName, { + required Future Function(String source) + restartEndpoint, + Future Function( + String source, + String lastAcceptedDill, + )? + reloadEndpoint, + required bool expectNewDeltaDill, + required String compiledIndicator, + }) { Future generateDeltaDill( - Future Function(String source) restartEndpoint, - String source) async { + Future Function(String source) restartEndpoint, + String source, + ) async { final result = await restartEndpoint(source); return result.deltaDill!; } @@ -54,8 +59,10 @@ void defineTests() { if (reloadEndpoint == null) { result = await restartEndpoint(sample); } else { - final lastAcceptedDill = - await generateDeltaDill(restartEndpoint, sample); + final lastAcceptedDill = await generateDeltaDill( + restartEndpoint, + sample, + ); result = await reloadEndpoint(sample, lastAcceptedDill); } expect(result.problems, isEmpty); @@ -68,20 +75,11 @@ void defineTests() { }; } - test( - 'simple', - generateEndpointTest(sampleCode), - ); + test('simple', generateEndpointTest(sampleCode)); - test( - 'with web', - generateEndpointTest(sampleCodeWeb), - ); + test('with web', generateEndpointTest(sampleCodeWeb)); - test( - 'with Flutter', - generateEndpointTest(sampleCodeFlutter), - ); + test('with Flutter', generateEndpointTest(sampleCodeFlutter)); test( 'with Flutter Counter', @@ -103,10 +101,7 @@ void defineTests() { generateEndpointTest(sampleCodeFlutterImplicitAnimations), ); - test( - 'with async', - generateEndpointTest(sampleCodeAsync), - ); + test('with async', generateEndpointTest(sampleCodeAsync)); test('with single error', () async { DDCCompilationResults result; @@ -117,8 +112,10 @@ void defineTests() { } expect(result.success, false); expect(result.problems.length, 1); - expect(result.problems[0].toString(), - contains('Error: Expected \';\' after this.')); + expect( + result.problems[0].toString(), + contains('Error: Expected \';\' after this.'), + ); }); test('with no main', () async { @@ -126,14 +123,18 @@ void defineTests() { if (reloadEndpoint == null) { result = await restartEndpoint(sampleCodeNoMain); } else { - final lastAcceptedDill = - await generateDeltaDill(restartEndpoint, sampleCode); + final lastAcceptedDill = await generateDeltaDill( + restartEndpoint, + sampleCode, + ); result = await reloadEndpoint(sampleCodeNoMain, lastAcceptedDill); } expect(result.success, false); expect(result.problems.length, 1); - expect(result.problems.first.message, - contains("Error: Method not found: 'main'")); + expect( + result.problems.first.message, + contains("Error: Method not found: 'main'"), + ); expect(result.problems.first.message, startsWith('main.dart:')); }); @@ -142,40 +143,53 @@ void defineTests() { if (reloadEndpoint == null) { result = await restartEndpoint(sampleCodeErrors); } else { - final lastAcceptedDill = - await generateDeltaDill(restartEndpoint, sampleCode); + final lastAcceptedDill = await generateDeltaDill( + restartEndpoint, + sampleCode, + ); result = await reloadEndpoint(sampleCodeErrors, lastAcceptedDill); } expect(result.success, false); expect(result.problems.length, 1); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print1\'.')); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print2\'.')); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print3\'.')); + expect( + result.problems[0].toString(), + contains('Error: Method not found: \'print1\'.'), + ); + expect( + result.problems[0].toString(), + contains('Error: Method not found: \'print2\'.'), + ); + expect( + result.problems[0].toString(), + contains('Error: Method not found: \'print3\'.'), + ); }); }); } - testDDCEndpoint('compileDDC', - restartEndpoint: (source) => compiler.compileDDC(source), - expectNewDeltaDill: false, - compiledIndicator: "define('dartpad_main', ["); + testDDCEndpoint( + 'compileDDC', + restartEndpoint: (source) => compiler.compileDDC(source), + expectNewDeltaDill: false, + compiledIndicator: "define('dartpad_main', [", + ); if (sdk.dartMajorVersion >= 3 && sdk.dartMinorVersion >= 8) { // DDC only supports these at version 3.8 and higher. - testDDCEndpoint('compileNewDDC', - restartEndpoint: (source) => compiler.compileNewDDC(source), - expectNewDeltaDill: true, - compiledIndicator: - 'defineLibrary("package:dartpad_sample/main.dart"'); - testDDCEndpoint('compileNewDDCReload', - restartEndpoint: (source) => compiler.compileNewDDC(source), - reloadEndpoint: (source, deltaDill) => - compiler.compileNewDDCReload(source, deltaDill), - expectNewDeltaDill: true, - compiledIndicator: - 'defineLibrary("package:dartpad_sample/main.dart"'); + testDDCEndpoint( + 'compileNewDDC', + restartEndpoint: (source) => compiler.compileNewDDC(source), + expectNewDeltaDill: true, + compiledIndicator: 'defineLibrary("package:dartpad_sample/main.dart"', + ); + testDDCEndpoint( + 'compileNewDDCReload', + restartEndpoint: (source) => compiler.compileNewDDC(source), + reloadEndpoint: + (source, deltaDill) => + compiler.compileNewDDCReload(source, deltaDill), + expectNewDeltaDill: true, + compiledIndicator: 'defineLibrary("package:dartpad_sample/main.dart"', + ); } test('sourcemap', () async { @@ -245,8 +259,10 @@ void main() { missingMethod ('foo'); } '''; final result = await compiler.compile(code); expect(result.problems, hasLength(1)); - expect(result.problems.single.message, - contains("Error when reading 'lib/foo.dart'")); + expect( + result.problems.single.message, + contains("Error when reading 'lib/foo.dart'"), + ); }); test('bad import - http', () async { @@ -256,8 +272,10 @@ void main() { missingMethod ('foo'); } '''; final result = await compiler.compile(code); expect(result.problems, hasLength(1)); - expect(result.problems.single.message, - contains("Error when reading 'http://example.com'")); + expect( + result.problems.single.message, + contains("Error when reading 'http://example.com'"), + ); }); test('multiple bad imports', () async { @@ -267,10 +285,14 @@ import 'package:bar'; '''; final result = await compiler.compile(code); expect(result.problems, hasLength(1)); - expect(result.problems.single.message, - contains("Invalid package URI 'package:foo'")); - expect(result.problems.single.message, - contains("Invalid package URI 'package:bar'")); + expect( + result.problems.single.message, + contains("Invalid package URI 'package:foo'"), + ); + expect( + result.problems.single.message, + contains("Invalid package URI 'package:bar'"), + ); }); test('disallow compiler warnings', () async { diff --git a/pkgs/dart_services/test/flutter_web_test.dart b/pkgs/dart_services/test/flutter_web_test.dart index ec83520d2..b122797a1 100644 --- a/pkgs/dart_services/test/flutter_web_test.dart +++ b/pkgs/dart_services/test/flutter_web_test.dart @@ -17,8 +17,13 @@ void defineTests() { group('FlutterWebManager', () { test('initializes', () async { expect(await Directory(projectTemplates.flutterPath).exists(), isTrue); - final file = File(path.join( - projectTemplates.flutterPath, '.dart_tool', 'package_config.json')); + final file = File( + path.join( + projectTemplates.flutterPath, + '.dart_tool', + 'package_config.json', + ), + ); expect(await file.exists(), isTrue); }); @@ -64,15 +69,22 @@ void defineTests() { group('flutter web project', () { test('packagesFilePath', () async { - final packageConfig = File(path.join( - projectTemplates.flutterPath, '.dart_tool', 'package_config.json')); + final packageConfig = File( + path.join( + projectTemplates.flutterPath, + '.dart_tool', + 'package_config.json', + ), + ); expect(await packageConfig.exists(), true); final encoded = await packageConfig.readAsString(); final contents = jsonDecode(encoded) as Map; expect(contents['packages'], isNotEmpty); final packages = contents['packages'] as List; - expect(packages.where((element) => (element as Map)['name'] == 'flutter'), - isNotEmpty); + expect( + packages.where((element) => (element as Map)['name'] == 'flutter'), + isNotEmpty, + ); }); test('summaryFilePath', () { diff --git a/pkgs/dart_services/test/genai_test.dart b/pkgs/dart_services/test/genai_test.dart index 2dfb230e4..f5ea9b2f9 100644 --- a/pkgs/dart_services/test/genai_test.dart +++ b/pkgs/dart_services/test/genai_test.dart @@ -27,23 +27,19 @@ $code }); test('handles code with markdown wrapper and some leading gunk', () async { - final input = Stream.fromIterable( - [ - 'some leading gunk\n', - ...wrappedCode.split('\n').map((line) => '$line\n') - ], - ); + final input = Stream.fromIterable([ + 'some leading gunk\n', + ...wrappedCode.split('\n').map((line) => '$line\n'), + ]); final cleaned = await GenerativeAI.cleanCode(input).join(); expect(cleaned.trim(), code.trim()); }); test('handles code with markdown wrapper and trailing gunk', () async { - final input = Stream.fromIterable( - [ - ...wrappedCode.split('\n').map((line) => '$line\n'), - 'some trailing gunk\n', - ], - ); + final input = Stream.fromIterable([ + ...wrappedCode.split('\n').map((line) => '$line\n'), + 'some trailing gunk\n', + ]); final cleaned = await GenerativeAI.cleanCode(input).join(); expect(cleaned.trim(), code.trim()); }); diff --git a/pkgs/dart_services/test/project_creator_test.dart b/pkgs/dart_services/test/project_creator_test.dart index 5158bf91f..e63d2b2d4 100644 --- a/pkgs/dart_services/test/project_creator_test.dart +++ b/pkgs/dart_services/test/project_creator_test.dart @@ -39,20 +39,13 @@ void defineTests() { }); test('project directory is created', () async { - await d.dir('project_templates', [ - d.dir('dart_project'), - ]).validate(); + await d.dir('project_templates', [d.dir('dart_project')]).validate(); }); test('pubspec is created', () async { await d.dir('project_templates', [ d.dir('dart_project', [ - d.file( - 'pubspec.yaml', - allOf([ - contains('sdk: ^$languageVersion'), - ]), - ), + d.file('pubspec.yaml', allOf([contains('sdk: ^$languageVersion')])), ]), ]).validate(); }); @@ -81,9 +74,7 @@ void defineTests() { }); test('project directory is created', () async { - await d.dir('project_templates', [ - d.dir('flutter_project'), - ]).validate(); + await d.dir('project_templates', [d.dir('flutter_project')]).validate(); }); test('Flutter Web directories are created', () async { @@ -91,7 +82,7 @@ void defineTests() { d.dir('flutter_project', [ d.dir('lib'), d.dir('web', [d.file('index.html', isEmpty)]), - ]) + ]), ]).validate(); }); diff --git a/pkgs/dart_services/test/pub_test.dart b/pkgs/dart_services/test/pub_test.dart index 03b704903..a11b00aa0 100644 --- a/pkgs/dart_services/test/pub_test.dart +++ b/pkgs/dart_services/test/pub_test.dart @@ -28,8 +28,10 @@ import 'dart:math'; import 'package:foo/foo.dart'; void main() { } '''; - expect(getAllImportsFor(source).map((import) => import.uri.stringValue), - unorderedEquals(['dart:math', 'package:foo/foo.dart'])); + expect( + getAllImportsFor(source).map((import) => import.uri.stringValue), + unorderedEquals(['dart:math', 'package:foo/foo.dart']), + ); }); test('two', () { @@ -41,9 +43,13 @@ import 'package:bar/bar.dart'; void main() { } '''; expect( - getAllImportsFor(source).map((import) => import.uri.stringValue), - unorderedEquals( - ['dart:math', 'package:foo/foo.dart', 'package:bar/bar.dart'])); + getAllImportsFor(source).map((import) => import.uri.stringValue), + unorderedEquals([ + 'dart:math', + 'package:foo/foo.dart', + 'package:bar/bar.dart', + ]), + ); }); test('three', () { @@ -56,14 +62,15 @@ import 'mybazfile.dart'; void main() { } '''; expect( - getAllImportsFor(source).map((import) => import.uri.stringValue), - unorderedEquals([ - 'dart:math', - 'package:foo/foo.dart', - 'package:bar/bar.dart', - 'package:baz/baz.dart', - 'mybazfile.dart' - ])); + getAllImportsFor(source).map((import) => import.uri.stringValue), + unorderedEquals([ + 'dart:math', + 'package:foo/foo.dart', + 'package:bar/bar.dart', + 'package:baz/baz.dart', + 'mybazfile.dart', + ]), + ); }); }); }); diff --git a/pkgs/dart_services/test/server_test.dart b/pkgs/dart_services/test/server_test.dart index 71b595f00..ac61250b0 100644 --- a/pkgs/dart_services/test/server_test.dart +++ b/pkgs/dart_services/test/server_test.dart @@ -44,16 +44,22 @@ void defineTests() { }); test('analyze', () async { - final result = await client.analyze(SourceRequest(source: ''' + final result = await client.analyze( + SourceRequest( + source: ''' void main() { print('hello world'); } -''')); +''', + ), + ); expect(result.issues, isEmpty); }); test('analyze flutter', () async { - final result = await client.analyze(SourceRequest(source: ''' + final result = await client.analyze( + SourceRequest( + source: ''' import 'package:flutter/material.dart'; void main() { @@ -72,69 +78,94 @@ class MyApp extends StatelessWidget { ); } } -''')); +''', + ), + ); expect(result, isNotNull); expect(result.issues, isEmpty); }); test('analyze errors', () async { - final result = await client.analyze(SourceRequest(source: r''' + final result = await client.analyze( + SourceRequest( + source: r''' void main() { int foo = 'bar'; print('hello world: $foo'); } -''')); +''', + ), + ); expect(result.issues, hasLength(1)); final issue = result.issues.first; expect(issue.kind, 'error'); expect( - issue.message, - contains( - "A value of type 'String' can't be assigned to a variable of type 'int'")); + issue.message, + contains( + "A value of type 'String' can't be assigned to a variable of type 'int'", + ), + ); expect(issue.location.line, 2); }); test('analyze unsupported import', () async { - final result = await client.analyze(SourceRequest(source: r''' + final result = await client.analyze( + SourceRequest( + source: r''' import 'package:foo_bar/foo_bar.dart'; void main() => print('hello world'); -''')); +''', + ), + ); expect(result.issues, isNotEmpty); final issue = result.issues.first; expect(issue.kind, 'warning'); expect( - issue.message, contains("Unsupported package: 'package:foo_bar'.")); + issue.message, + contains("Unsupported package: 'package:foo_bar'."), + ); expect(issue.location.line, 1); }); test('analyze firebase import', () async { - final result = await client.analyze(SourceRequest(source: r''' + final result = await client.analyze( + SourceRequest( + source: r''' import 'package:firebase_core/firebase_core.dart'; void main() => print('hello world'); -''')); +''', + ), + ); expect(result.issues, isNotEmpty); final issue = result.issues.first; expect(issue.kind, 'warning'); - expect(issue.message, - contains('Firebase is no longer supported by DartPad.')); + expect( + issue.message, + contains('Firebase is no longer supported by DartPad.'), + ); expect(issue.location.line, 1); }); test('complete', () async { - final result = await client.complete(SourceRequest(source: ''' + final result = await client.complete( + SourceRequest( + source: ''' void main() { print('hello world'); } -''', offset: 18)); +''', + offset: 18, + ), + ); expect(result.replacementOffset, 16); expect(result.replacementLength, 5); @@ -152,9 +183,13 @@ void main() { }); test('format', () async { - final result = await client.format(SourceRequest(source: ''' + final result = await client.format( + SourceRequest( + source: ''' void main() { print('hello world'); } -''')); +''', + ), + ); expect(result.source, ''' void main() { @@ -164,11 +199,15 @@ void main() { }); test('format no changes', () async { - final result = await client.format(SourceRequest(source: ''' + final result = await client.format( + SourceRequest( + source: ''' void main() { print('hello world'); } -''')); +''', + ), + ); expect(result.source, ''' void main() { @@ -178,12 +217,14 @@ void main() { }); test('format preserves offset', () async { - final result = await client.format(SourceRequest( - source: ''' + final result = await client.format( + SourceRequest( + source: ''' void main() { print('hello world'); } ''', - offset: 15, - )); + offset: 15, + ), + ); expect(result.source, ''' void main() { @@ -194,39 +235,54 @@ void main() { }); test('compile', () async { - final result = await client.compile(CompileRequest(source: ''' + final result = await client.compile( + CompileRequest( + source: ''' void main() { print('hello world'); } -''')); +''', + ), + ); expect(result.result, isNotEmpty); expect(result.result.length, greaterThanOrEqualTo(10 * 1024)); }); test('compile with error', () async { try { - await client.compile(CompileRequest(source: ''' + await client.compile( + CompileRequest( + source: ''' void main() { print('hello world') } -''')); +''', + ), + ); fail('compile error expected'); } on ApiRequestError catch (e) { expect(e.body, contains("Expected ';' after this.")); } }); - void testDDCEndpoint(String endpointName, - Future Function(CompileRequest) endpoint, - {required bool expectDeltaDill, - Future Function(String source)? generateLastAcceptedDill}) { + void testDDCEndpoint( + String endpointName, + Future Function(CompileRequest) endpoint, { + required bool expectDeltaDill, + Future Function(String source)? generateLastAcceptedDill, + }) { group(endpointName, () { test('compile', () async { - final result = await endpoint(CompileRequest(source: ''' + final result = await endpoint( + CompileRequest( + source: ''' void main() { print('hello world'); } -''', deltaDill: await generateLastAcceptedDill?.call(sampleCode))); +''', + deltaDill: await generateLastAcceptedDill?.call(sampleCode), + ), + ); expect(result.result, isNotEmpty); expect(result.result.length, greaterThanOrEqualTo(1024)); expect(result.modulesBaseUrl, isNotEmpty); @@ -234,7 +290,9 @@ void main() { }); test('compile flutter', () async { - final result = await endpoint(CompileRequest(source: ''' + final result = await endpoint( + CompileRequest( + source: ''' import 'package:flutter/material.dart'; void main() { @@ -251,7 +309,10 @@ class MyApp extends StatelessWidget { ); } } -''', deltaDill: await generateLastAcceptedDill?.call(sampleCode))); +''', + deltaDill: await generateLastAcceptedDill?.call(sampleCode), + ), + ); expect(result.result, isNotEmpty); expect(result.result.length, greaterThanOrEqualTo(10 * 1024)); expect(result.modulesBaseUrl, isNotEmpty); @@ -259,11 +320,16 @@ class MyApp extends StatelessWidget { test('compile with error', () async { try { - await endpoint(CompileRequest(source: ''' + await endpoint( + CompileRequest( + source: ''' void main() { print('hello world') } -''', deltaDill: await generateLastAcceptedDill?.call(sampleCode))); +''', + deltaDill: await generateLastAcceptedDill?.call(sampleCode), + ), + ); fail('compile error expected'); } on ApiRequestError catch (e) { expect(e.body, contains("Expected ';' after this.")); @@ -272,29 +338,40 @@ void main() { }); } - testDDCEndpoint('compileDDC', (request) => client.compileDDC(request), - expectDeltaDill: false); + testDDCEndpoint( + 'compileDDC', + (request) => client.compileDDC(request), + expectDeltaDill: false, + ); if (sdk.dartMajorVersion >= 3 && sdk.dartMinorVersion >= 8) { testDDCEndpoint( - 'compileNewDDC', (request) => client.compileNewDDC(request), - expectDeltaDill: true); - testDDCEndpoint('compileNewDDCReload', - (request) => client.compileNewDDCReload(request), - expectDeltaDill: true, - generateLastAcceptedDill: (source) async => - (await client.compileNewDDC(CompileRequest(source: source))) - .deltaDill!); + 'compileNewDDC', + (request) => client.compileNewDDC(request), + expectDeltaDill: true, + ); + testDDCEndpoint( + 'compileNewDDCReload', + (request) => client.compileNewDDCReload(request), + expectDeltaDill: true, + generateLastAcceptedDill: + (source) async => + (await client.compileNewDDC( + CompileRequest(source: source), + )).deltaDill!, + ); } test('document', () async { - final result = await client.document(SourceRequest( - source: ''' + final result = await client.document( + SourceRequest( + source: ''' void main() { print('hello world'); } ''', - offset: 18, - )); + offset: 18, + ), + ); expect( result.dartdoc!.toLowerCase(), @@ -308,14 +385,16 @@ void main() { }); test('document empty', () async { - final result = await client.document(SourceRequest( - source: ''' + final result = await client.document( + SourceRequest( + source: ''' void main() { print('hello world'); } ''', - offset: 15, - )); + offset: 15, + ), + ); expect(result.dartdoc, isNull); expect(result.elementKind, isNull); @@ -323,55 +402,65 @@ void main() { }); test('fixes', () async { - final result = await client.fixes(SourceRequest( - source: ''' + final result = await client.fixes( + SourceRequest( + source: ''' void main() { var foo = 'bar'; print('hello world'); } ''', - offset: 21, - )); + offset: 21, + ), + ); // Dart 3.5 returns 3 fixes; Dart 3.6 returns 4. expect(result.fixes, anyOf(hasLength(3), hasLength(4))); - final fix = result.fixes - .firstWhereOrNull((fix) => fix.message.contains('Ignore')); + final fix = result.fixes.firstWhereOrNull( + (fix) => fix.message.contains('Ignore'), + ); expect(fix, isNotNull); expect(fix!.edits, hasLength(1)); expect(fix.linkedEditGroups, isEmpty); - expect(fix.edits.first.replacement, - contains('// ignore: unused_local_variable')); + expect( + fix.edits.first.replacement, + contains('// ignore: unused_local_variable'), + ); }); test('fixes empty', () async { - final result = await client.fixes(SourceRequest( - source: ''' + final result = await client.fixes( + SourceRequest( + source: ''' void main() { var foo = 'bar'; print(foo); } ''', - offset: 21, - )); + offset: 21, + ), + ); expect(result.fixes, hasLength(0)); }); test('assists', () async { - final result = await client.fixes(SourceRequest( - source: ''' + final result = await client.fixes( + SourceRequest( + source: ''' void main() => print('hello world'); ''', - offset: 13, - )); + offset: 13, + ), + ); expect(result.fixes, hasLength(0)); expect(result.assists, isNotEmpty); final assist = result.assists.firstWhereOrNull( - (assist) => assist.message.contains('Convert to block body')); + (assist) => assist.message.contains('Convert to block body'), + ); expect(assist, isNotNull); expect(assist!.edits, hasLength(1)); expect(assist.linkedEditGroups, isEmpty); diff --git a/pkgs/dart_services/test/shelf_cors_test.dart b/pkgs/dart_services/test/shelf_cors_test.dart index 59951bc98..39d997309 100644 --- a/pkgs/dart_services/test/shelf_cors_test.dart +++ b/pkgs/dart_services/test/shelf_cors_test.dart @@ -13,8 +13,10 @@ void defineTests() { return shelf.Response.ok('OK'); } - final request = - shelf.Request('GET', Uri.parse('http://example.com/index.html')); + final request = shelf.Request( + 'GET', + Uri.parse('http://example.com/index.html'), + ); group('shelf_cors', () { test('adds default CORS headers to the response', () async { @@ -30,18 +32,23 @@ void defineTests() { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': - 'Origin, X-Requested-With, Content-Type, Accept' + 'Origin, X-Requested-With, Content-Type, Accept', }; - final middleware = - shelf_cors.createCorsHeadersMiddleware(corsHeaders: corsHeaders); + final middleware = shelf_cors.createCorsHeadersMiddleware( + corsHeaders: corsHeaders, + ); final handler = middleware(handleAll); final response = await handler(request); expect(response.headers['Access-Control-Allow-Origin'], equals('*')); - expect(response.headers['Access-Control-Allow-Methods'], - equals('POST, OPTIONS')); - expect(response.headers['Access-Control-Allow-Headers'], - equals('Origin, X-Requested-With, Content-Type, Accept')); + expect( + response.headers['Access-Control-Allow-Methods'], + equals('POST, OPTIONS'), + ); + expect( + response.headers['Access-Control-Allow-Headers'], + equals('Origin, X-Requested-With, Content-Type, Accept'), + ); }); }); } diff --git a/pkgs/dart_services/test/utils_test.dart b/pkgs/dart_services/test/utils_test.dart index 2cacfa7f2..3f65e6a23 100644 --- a/pkgs/dart_services/test/utils_test.dart +++ b/pkgs/dart_services/test/utils_test.dart @@ -42,10 +42,7 @@ void defineTests() { }); test('keeps a "dart:core" path intact', () { - expectNormalizeFilePaths( - 'dart:core/foo.dart', - 'dart:core/foo.dart', - ); + expectNormalizeFilePaths('dart:core/foo.dart', 'dart:core/foo.dart'); }); test('keeps a web URL intact', () { diff --git a/pkgs/dart_services/tool/grind.dart b/pkgs/dart_services/tool/grind.dart index 0ce070a95..ab6933081 100644 --- a/pkgs/dart_services/tool/grind.dart +++ b/pkgs/dart_services/tool/grind.dart @@ -22,10 +22,7 @@ Future main(List args) async { return grind(args); } -final List compilationArtifacts = [ - 'dart_sdk.js', - 'flutter_web.js', -]; +final List compilationArtifacts = ['dart_sdk.js', 'flutter_web.js']; final List compilationArtifactsNew = [ 'dart_sdk_new.js', @@ -33,8 +30,10 @@ final List compilationArtifactsNew = [ 'ddc_module_loader.js', ]; -@Task('validate that we have the correct compilation artifacts available in ' - 'google storage') +@Task( + 'validate that we have the correct compilation artifacts available in ' + 'google storage', +) void validateStorageArtifacts() async { final args = context.invocation.arguments; final sdk = Sdk.fromLocalFlutter(); @@ -45,7 +44,8 @@ void validateStorageArtifacts() async { }; print( - 'validate-storage-artifacts version: ${sdk.dartVersion} bucket: $bucket'); + 'validate-storage-artifacts version: ${sdk.dartVersion} bucket: $bucket', + ); final urlBase = 'https://storage.googleapis.com/$bucket/'; for (final artifact @@ -103,8 +103,9 @@ void buildStorageArtifacts() async { final temp = Directory.systemTemp.createTempSync('flutter_web_sample'); try { - instructions - .add(await _buildStorageArtifacts(temp, sdk, channel: sdk.channel)); + instructions.add( + await _buildStorageArtifacts(temp, sdk, channel: sdk.channel), + ); } finally { temp.deleteSync(recursive: true); } @@ -167,12 +168,14 @@ Future _buildStorageArtifacts( // Working around Flutter 3.3's deprecation of generated_plugin_registrant.dart // Context: https://github.com/flutter/flutter/pull/106921 - final pluginRegistrant = File(path.join( - dir.path, '.dart_tool', 'dartpad', 'web_plugin_registrant.dart')); + final pluginRegistrant = File( + path.join(dir.path, '.dart_tool', 'dartpad', 'web_plugin_registrant.dart'), + ); if (pluginRegistrant.existsSync()) { Directory(path.join(dir.path, 'lib')).createSync(); pluginRegistrant.copySync( - path.join(dir.path, 'lib', 'generated_plugin_registrant.dart')); + path.join(dir.path, 'lib', 'generated_plugin_registrant.dart'), + ); } final flutterLibraries = []; @@ -210,8 +213,10 @@ Future _buildStorageArtifacts( // Later versions of Flutter remove the "sound" suffix from files. If the // suffixed version does not exist, the unsuffixed version is the sound file. var dillPath = path.join(sdk.flutterWebSdkPath, 'ddc_outline_sound.dill'); - var sdkJsPath = - path.join(sdk.flutterWebSdkPath, 'amd-canvaskit-sound/dart_sdk.js'); + var sdkJsPath = path.join( + sdk.flutterWebSdkPath, + 'amd-canvaskit-sound/dart_sdk.js', + ); if (!getFile(dillPath).existsSync()) { dillPath = path.join(sdk.flutterWebSdkPath, 'ddc_outline.dill'); sdkJsPath = path.join(sdk.flutterWebSdkPath, 'amd-canvaskit/dart_sdk.js'); @@ -225,14 +230,10 @@ Future _buildStorageArtifacts( '--source-map', '-o', 'flutter_web.js', - ...flutterLibraries + ...flutterLibraries, ]; - await _run( - compilerPath, - arguments: arguments, - workingDirectory: dir.path, - ); + await _run(compilerPath, arguments: arguments, workingDirectory: dir.path); // Copy all to the project directory. final artifactsDir = getDir(path.join('artifacts')); @@ -250,13 +251,19 @@ Future _buildStorageArtifacts( // the suffixed version does not exist, the unsuffixed version is the sound // file. var newSdkJsPath = path.join( - sdk.flutterWebSdkPath, 'ddcLibraryBundle-canvaskit-sound/dart_sdk.js'); + sdk.flutterWebSdkPath, + 'ddcLibraryBundle-canvaskit-sound/dart_sdk.js', + ); if (!getFile(newSdkJsPath).existsSync()) { newSdkJsPath = path.join( - sdk.flutterWebSdkPath, 'ddcLibraryBundle-canvaskit/dart_sdk.js'); + sdk.flutterWebSdkPath, + 'ddcLibraryBundle-canvaskit/dart_sdk.js', + ); } - final ddcModuleLoaderPath = - path.join(sdk.dartSdkPath, 'lib/dev_compiler/ddc/ddc_module_loader.js'); + final ddcModuleLoaderPath = path.join( + sdk.dartSdkPath, + 'lib/dev_compiler/ddc/ddc_module_loader.js', + ); final argumentsNew = [ path.join(sdk.dartSdkPath, 'bin', 'snapshots', 'dartdevc.dart.snapshot'), @@ -269,7 +276,7 @@ Future _buildStorageArtifacts( '--no-summarize', '-o', 'flutter_web_new.js', - ...flutterLibraries + ...flutterLibraries, ]; await _run( @@ -281,10 +288,12 @@ Future _buildStorageArtifacts( copy(getFile(ddcModuleLoaderPath), artifactsDir); copy(getFile(newSdkJsPath), artifactsDir); copy(getFile('$newSdkJsPath.map'), artifactsDir); - joinFile(artifactsDir, ['dart_sdk.js']) - .copySync(path.join('artifacts', 'dart_sdk_new.js')); - joinFile(artifactsDir, ['dart_sdk.js.map']) - .copySync(path.join('artifacts', 'dart_sdk_new.js.map')); + joinFile(artifactsDir, [ + 'dart_sdk.js', + ]).copySync(path.join('artifacts', 'dart_sdk_new.js')); + joinFile(artifactsDir, [ + 'dart_sdk.js.map', + ]).copySync(path.join('artifacts', 'dart_sdk_new.js.map')); copy(joinFile(dir, ['flutter_web_new.js']), artifactsDir); copy(joinFile(dir, ['flutter_web_new.js.map']), artifactsDir); @@ -315,11 +324,13 @@ Future _run( String? workingDirectory, Map environment = const {}, }) async { - final process = await runWithLogging(executable, - arguments: arguments, - workingDirectory: workingDirectory, - environment: environment, - log: log); + final process = await runWithLogging( + executable, + arguments: arguments, + workingDirectory: workingDirectory, + environment: environment, + log: log, + ); final exitCode = await process.exitCode; if (exitCode != 0) { fail('Unable to exec $executable, failed with code $exitCode'); diff --git a/pkgs/dartpad_shared/lib/model.dart b/pkgs/dartpad_shared/lib/model.dart index f52c584a1..03e3750e6 100644 --- a/pkgs/dartpad_shared/lib/model.dart +++ b/pkgs/dartpad_shared/lib/model.dart @@ -29,9 +29,7 @@ class SourceRequest { class AnalysisResponse { final List issues; - AnalysisResponse({ - required this.issues, - }); + AnalysisResponse({required this.issues}); factory AnalysisResponse.fromJson(Map json) => _$AnalysisResponseFromJson(json); @@ -108,10 +106,7 @@ class DiagnosticMessage { final String message; final Location location; - DiagnosticMessage({ - required this.message, - required this.location, - }); + DiagnosticMessage({required this.message, required this.location}); factory DiagnosticMessage.fromJson(Map json) => _$DiagnosticMessageFromJson(json); @@ -167,10 +162,7 @@ class FormatResponse { final String source; final int? offset; - FormatResponse({ - required this.source, - required this.offset, - }); + FormatResponse({required this.source, required this.offset}); factory FormatResponse.fromJson(Map json) => _$FormatResponseFromJson(json); @@ -183,18 +175,12 @@ class FormatResponse { @JsonSerializable() class FixesResponse { - static final FixesResponse empty = FixesResponse( - fixes: [], - assists: [], - ); + static final FixesResponse empty = FixesResponse(fixes: [], assists: []); final List fixes; final List assists; - FixesResponse({ - required this.fixes, - required this.assists, - }); + FixesResponse({required this.fixes, required this.assists}); factory FixesResponse.fromJson(Map json) => _$FixesResponseFromJson(json); @@ -269,10 +255,7 @@ class LinkedEditSuggestion { final String value; final String kind; - LinkedEditSuggestion({ - required this.value, - required this.kind, - }); + LinkedEditSuggestion({required this.value, required this.kind}); factory LinkedEditSuggestion.fromJson(Map json) => _$LinkedEditSuggestionFromJson(json); @@ -391,9 +374,7 @@ class VersionResponse { class OpenInIdxRequest { final String code; - OpenInIdxRequest({ - required this.code, - }); + OpenInIdxRequest({required this.code}); factory OpenInIdxRequest.fromJson(Map json) => _$OpenInIdxRequestFromJson(json); @@ -408,9 +389,7 @@ class OpenInIdxRequest { class OpenInIdxResponse { final String idxUrl; - OpenInIdxResponse({ - required this.idxUrl, - }); + OpenInIdxResponse({required this.idxUrl}); factory OpenInIdxResponse.fromJson(Map json) => _$OpenInIdxResponseFromJson(json); @@ -461,7 +440,8 @@ class SuggestFixRequest { Map toJson() => _$SuggestFixRequestToJson(this); @override - String toString() => 'SuggestFixRequest ' + String toString() => + 'SuggestFixRequest ' '[$errorMessage] ' '[${source.substring(0, 10)} (...)'; } @@ -529,8 +509,8 @@ class Attachment { required this.name, required Uint8List bytes, required this.mimeType, - }) : base64EncodedBytes = base64Encode(bytes), - _cachedBytes = bytes; + }) : base64EncodedBytes = base64Encode(bytes), + _cachedBytes = bytes; final String name; final String base64EncodedBytes; diff --git a/pkgs/dartpad_shared/lib/model.g.dart b/pkgs/dartpad_shared/lib/model.g.dart index b15182605..0ed26427d 100644 --- a/pkgs/dartpad_shared/lib/model.g.dart +++ b/pkgs/dartpad_shared/lib/model.g.dart @@ -13,22 +13,18 @@ SourceRequest _$SourceRequestFromJson(Map json) => ); Map _$SourceRequestToJson(SourceRequest instance) => - { - 'source': instance.source, - 'offset': instance.offset, - }; + {'source': instance.source, 'offset': instance.offset}; AnalysisResponse _$AnalysisResponseFromJson(Map json) => AnalysisResponse( - issues: (json['issues'] as List) - .map((e) => AnalysisIssue.fromJson(e as Map)) - .toList(), + issues: + (json['issues'] as List) + .map((e) => AnalysisIssue.fromJson(e as Map)) + .toList(), ); Map _$AnalysisResponseToJson(AnalysisResponse instance) => - { - 'issues': instance.issues, - }; + {'issues': instance.issues}; AnalysisIssue _$AnalysisIssueFromJson(Map json) => AnalysisIssue( @@ -38,9 +34,12 @@ AnalysisIssue _$AnalysisIssueFromJson(Map json) => code: json['code'] as String?, correction: json['correction'] as String?, url: json['url'] as String?, - contextMessages: (json['contextMessages'] as List?) - ?.map((e) => DiagnosticMessage.fromJson(e as Map)) - .toList(), + contextMessages: + (json['contextMessages'] as List?) + ?.map( + (e) => DiagnosticMessage.fromJson(e as Map), + ) + .toList(), hasFix: json['hasFix'] as bool?, ); @@ -57,18 +56,18 @@ Map _$AnalysisIssueToJson(AnalysisIssue instance) => }; Location _$LocationFromJson(Map json) => Location( - charStart: (json['charStart'] as num?)?.toInt() ?? -1, - charLength: (json['charLength'] as num?)?.toInt() ?? 0, - line: (json['line'] as num?)?.toInt() ?? -1, - column: (json['column'] as num?)?.toInt() ?? -1, - ); + charStart: (json['charStart'] as num?)?.toInt() ?? -1, + charLength: (json['charLength'] as num?)?.toInt() ?? 0, + line: (json['line'] as num?)?.toInt() ?? -1, + column: (json['column'] as num?)?.toInt() ?? -1, +); Map _$LocationToJson(Location instance) => { - 'charStart': instance.charStart, - 'charLength': instance.charLength, - 'line': instance.line, - 'column': instance.column, - }; + 'charStart': instance.charStart, + 'charLength': instance.charLength, + 'line': instance.line, + 'column': instance.column, +}; DiagnosticMessage _$DiagnosticMessageFromJson(Map json) => DiagnosticMessage( @@ -95,14 +94,10 @@ Map _$CompileRequestToJson(CompileRequest instance) => }; CompileResponse _$CompileResponseFromJson(Map json) => - CompileResponse( - result: json['result'] as String, - ); + CompileResponse(result: json['result'] as String); Map _$CompileResponseToJson(CompileResponse instance) => - { - 'result': instance.result, - }; + {'result': instance.result}; CompileDDCResponse _$CompileDDCResponseFromJson(Map json) => CompileDDCResponse( @@ -125,37 +120,35 @@ FormatResponse _$FormatResponseFromJson(Map json) => ); Map _$FormatResponseToJson(FormatResponse instance) => - { - 'source': instance.source, - 'offset': instance.offset, - }; + {'source': instance.source, 'offset': instance.offset}; FixesResponse _$FixesResponseFromJson(Map json) => FixesResponse( - fixes: (json['fixes'] as List) - .map((e) => SourceChange.fromJson(e as Map)) - .toList(), - assists: (json['assists'] as List) - .map((e) => SourceChange.fromJson(e as Map)) - .toList(), + fixes: + (json['fixes'] as List) + .map((e) => SourceChange.fromJson(e as Map)) + .toList(), + assists: + (json['assists'] as List) + .map((e) => SourceChange.fromJson(e as Map)) + .toList(), ); Map _$FixesResponseToJson(FixesResponse instance) => - { - 'fixes': instance.fixes, - 'assists': instance.assists, - }; + {'fixes': instance.fixes, 'assists': instance.assists}; SourceChange _$SourceChangeFromJson(Map json) => SourceChange( - message: json['message'] as String, - edits: (json['edits'] as List) + message: json['message'] as String, + edits: + (json['edits'] as List) .map((e) => SourceEdit.fromJson(e as Map)) .toList(), - linkedEditGroups: (json['linkedEditGroups'] as List) + linkedEditGroups: + (json['linkedEditGroups'] as List) .map((e) => LinkedEditGroup.fromJson(e as Map)) .toList(), - selectionOffset: (json['selectionOffset'] as num?)?.toInt(), - ); + selectionOffset: (json['selectionOffset'] as num?)?.toInt(), +); Map _$SourceChangeToJson(SourceChange instance) => { @@ -166,10 +159,10 @@ Map _$SourceChangeToJson(SourceChange instance) => }; SourceEdit _$SourceEditFromJson(Map json) => SourceEdit( - offset: (json['offset'] as num).toInt(), - length: (json['length'] as num).toInt(), - replacement: json['replacement'] as String, - ); + offset: (json['offset'] as num).toInt(), + length: (json['length'] as num).toInt(), + replacement: json['replacement'] as String, +); Map _$SourceEditToJson(SourceEdit instance) => { @@ -180,13 +173,17 @@ Map _$SourceEditToJson(SourceEdit instance) => LinkedEditGroup _$LinkedEditGroupFromJson(Map json) => LinkedEditGroup( - offsets: (json['offsets'] as List) - .map((e) => (e as num).toInt()) - .toList(), + offsets: + (json['offsets'] as List) + .map((e) => (e as num).toInt()) + .toList(), length: (json['length'] as num).toInt(), - suggestions: (json['suggestions'] as List) - .map((e) => LinkedEditSuggestion.fromJson(e as Map)) - .toList(), + suggestions: + (json['suggestions'] as List) + .map( + (e) => LinkedEditSuggestion.fromJson(e as Map), + ) + .toList(), ); Map _$LinkedEditGroupToJson(LinkedEditGroup instance) => @@ -197,18 +194,15 @@ Map _$LinkedEditGroupToJson(LinkedEditGroup instance) => }; LinkedEditSuggestion _$LinkedEditSuggestionFromJson( - Map json) => - LinkedEditSuggestion( - value: json['value'] as String, - kind: json['kind'] as String, - ); + Map json, +) => LinkedEditSuggestion( + value: json['value'] as String, + kind: json['kind'] as String, +); Map _$LinkedEditSuggestionToJson( - LinkedEditSuggestion instance) => - { - 'value': instance.value, - 'kind': instance.kind, - }; + LinkedEditSuggestion instance, +) => {'value': instance.value, 'kind': instance.kind}; DocumentResponse _$DocumentResponseFromJson(Map json) => DocumentResponse( @@ -234,9 +228,12 @@ CompleteResponse _$CompleteResponseFromJson(Map json) => CompleteResponse( replacementOffset: (json['replacementOffset'] as num).toInt(), replacementLength: (json['replacementLength'] as num).toInt(), - suggestions: (json['suggestions'] as List) - .map((e) => CompletionSuggestion.fromJson(e as Map)) - .toList(), + suggestions: + (json['suggestions'] as List) + .map( + (e) => CompletionSuggestion.fromJson(e as Map), + ) + .toList(), ); Map _$CompleteResponseToJson(CompleteResponse instance) => @@ -247,36 +244,37 @@ Map _$CompleteResponseToJson(CompleteResponse instance) => }; CompletionSuggestion _$CompletionSuggestionFromJson( - Map json) => - CompletionSuggestion( - kind: json['kind'] as String, - relevance: (json['relevance'] as num).toInt(), - completion: json['completion'] as String, - deprecated: json['deprecated'] as bool, - selectionOffset: (json['selectionOffset'] as num).toInt(), - displayText: json['displayText'] as String?, - parameterNames: (json['parameterNames'] as List?) + Map json, +) => CompletionSuggestion( + kind: json['kind'] as String, + relevance: (json['relevance'] as num).toInt(), + completion: json['completion'] as String, + deprecated: json['deprecated'] as bool, + selectionOffset: (json['selectionOffset'] as num).toInt(), + displayText: json['displayText'] as String?, + parameterNames: + (json['parameterNames'] as List?) ?.map((e) => e as String) .toList(), - returnType: json['returnType'] as String?, - elementKind: json['elementKind'] as String?, - elementParameters: json['elementParameters'] as String?, - ); + returnType: json['returnType'] as String?, + elementKind: json['elementKind'] as String?, + elementParameters: json['elementParameters'] as String?, +); Map _$CompletionSuggestionToJson( - CompletionSuggestion instance) => - { - 'kind': instance.kind, - 'relevance': instance.relevance, - 'completion': instance.completion, - 'deprecated': instance.deprecated, - 'selectionOffset': instance.selectionOffset, - 'displayText': instance.displayText, - 'parameterNames': instance.parameterNames, - 'returnType': instance.returnType, - 'elementKind': instance.elementKind, - 'elementParameters': instance.elementParameters, - }; + CompletionSuggestion instance, +) => { + 'kind': instance.kind, + 'relevance': instance.relevance, + 'completion': instance.completion, + 'deprecated': instance.deprecated, + 'selectionOffset': instance.selectionOffset, + 'displayText': instance.displayText, + 'parameterNames': instance.parameterNames, + 'returnType': instance.returnType, + 'elementKind': instance.elementKind, + 'elementParameters': instance.elementParameters, +}; VersionResponse _$VersionResponseFromJson(Map json) => VersionResponse( @@ -284,12 +282,14 @@ VersionResponse _$VersionResponseFromJson(Map json) => flutterVersion: json['flutterVersion'] as String, engineVersion: json['engineVersion'] as String, serverRevision: json['serverRevision'] as String?, - experiments: (json['experiments'] as List) - .map((e) => e as String) - .toList(), - packages: (json['packages'] as List) - .map((e) => PackageInfo.fromJson(e as Map)) - .toList(), + experiments: + (json['experiments'] as List) + .map((e) => e as String) + .toList(), + packages: + (json['packages'] as List) + .map((e) => PackageInfo.fromJson(e as Map)) + .toList(), ); Map _$VersionResponseToJson(VersionResponse instance) => @@ -303,30 +303,22 @@ Map _$VersionResponseToJson(VersionResponse instance) => }; OpenInIdxRequest _$OpenInIdxRequestFromJson(Map json) => - OpenInIdxRequest( - code: json['code'] as String, - ); + OpenInIdxRequest(code: json['code'] as String); Map _$OpenInIdxRequestToJson(OpenInIdxRequest instance) => - { - 'code': instance.code, - }; + {'code': instance.code}; OpenInIdxResponse _$OpenInIdxResponseFromJson(Map json) => - OpenInIdxResponse( - idxUrl: json['idxUrl'] as String, - ); + OpenInIdxResponse(idxUrl: json['idxUrl'] as String); Map _$OpenInIdxResponseToJson(OpenInIdxResponse instance) => - { - 'idxUrl': instance.idxUrl, - }; + {'idxUrl': instance.idxUrl}; PackageInfo _$PackageInfoFromJson(Map json) => PackageInfo( - name: json['name'] as String, - version: json['version'] as String, - supported: json['supported'] as bool, - ); + name: json['name'] as String, + version: json['version'] as String, + supported: json['supported'] as bool, +); Map _$PackageInfoToJson(PackageInfo instance) => { @@ -357,32 +349,31 @@ GenerateCodeRequest _$GenerateCodeRequestFromJson(Map json) => GenerateCodeRequest( appType: $enumDecode(_$AppTypeEnumMap, json['appType']), prompt: json['prompt'] as String, - attachments: (json['attachments'] as List) - .map((e) => Attachment.fromJson(e as Map)) - .toList(), + attachments: + (json['attachments'] as List) + .map((e) => Attachment.fromJson(e as Map)) + .toList(), ); Map _$GenerateCodeRequestToJson( - GenerateCodeRequest instance) => - { - 'appType': _$AppTypeEnumMap[instance.appType]!, - 'prompt': instance.prompt, - 'attachments': instance.attachments, - }; - -const _$AppTypeEnumMap = { - AppType.dart: 'dart', - AppType.flutter: 'flutter', + GenerateCodeRequest instance, +) => { + 'appType': _$AppTypeEnumMap[instance.appType]!, + 'prompt': instance.prompt, + 'attachments': instance.attachments, }; +const _$AppTypeEnumMap = {AppType.dart: 'dart', AppType.flutter: 'flutter'}; + UpdateCodeRequest _$UpdateCodeRequestFromJson(Map json) => UpdateCodeRequest( appType: $enumDecode(_$AppTypeEnumMap, json['appType']), prompt: json['prompt'] as String, source: json['source'] as String, - attachments: (json['attachments'] as List) - .map((e) => Attachment.fromJson(e as Map)) - .toList(), + attachments: + (json['attachments'] as List) + .map((e) => Attachment.fromJson(e as Map)) + .toList(), ); Map _$UpdateCodeRequestToJson(UpdateCodeRequest instance) => @@ -394,10 +385,10 @@ Map _$UpdateCodeRequestToJson(UpdateCodeRequest instance) => }; Attachment _$AttachmentFromJson(Map json) => Attachment( - name: json['name'] as String, - base64EncodedBytes: json['base64EncodedBytes'] as String, - mimeType: json['mimeType'] as String, - ); + name: json['name'] as String, + base64EncodedBytes: json['base64EncodedBytes'] as String, + mimeType: json['mimeType'] as String, +); Map _$AttachmentToJson(Attachment instance) => { diff --git a/pkgs/dartpad_shared/lib/services.dart b/pkgs/dartpad_shared/lib/services.dart index 280264ad8..5eced517c 100644 --- a/pkgs/dartpad_shared/lib/services.dart +++ b/pkgs/dartpad_shared/lib/services.dart @@ -42,11 +42,17 @@ class ServicesClient { Future compileNewDDC(CompileRequest request) => _requestPost( - 'compileNewDDC', request.toJson(), CompileDDCResponse.fromJson); + 'compileNewDDC', + request.toJson(), + CompileDDCResponse.fromJson, + ); Future compileNewDDCReload(CompileRequest request) => _requestPost( - 'compileNewDDCReload', request.toJson(), CompileDDCResponse.fromJson); + 'compileNewDDCReload', + request.toJson(), + CompileDDCResponse.fromJson, + ); Future openInIdx(OpenInIdxRequest request) => _requestPost('openInIDX', request.toJson(), OpenInIdxResponse.fromJson); @@ -73,7 +79,8 @@ class ServicesClient { } else { try { return responseFactory( - json.decode(response.body) as Map); + json.decode(response.body) as Map, + ); } on FormatException catch (e) { throw ApiRequestError('$action: $e', response.body); } @@ -95,7 +102,8 @@ class ServicesClient { } else { try { return responseFactory( - json.decode(response.body) as Map); + json.decode(response.body) as Map, + ); } on FormatException catch (e) { throw ApiRequestError('$action: $e', response.body); } @@ -106,10 +114,7 @@ class ServicesClient { String action, Map request, ) async* { - final httpRequest = Request( - 'POST', - Uri.parse('${rootUrl}api/v3/$action'), - ); + final httpRequest = Request('POST', Uri.parse('${rootUrl}api/v3/$action')); httpRequest.encoding = utf8; httpRequest.headers['Content-Type'] = 'application/json'; httpRequest.body = json.encode(request); diff --git a/pkgs/dartpad_shared/pubspec.yaml b/pkgs/dartpad_shared/pubspec.yaml index e4aa8c050..d0ed7be8f 100644 --- a/pkgs/dartpad_shared/pubspec.yaml +++ b/pkgs/dartpad_shared/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.6.1 + sdk: ^3.7.0 dependencies: http: ^1.3.0 diff --git a/pkgs/dartpad_ui/lib/console.dart b/pkgs/dartpad_ui/lib/console.dart index f04b2789f..d3cd8c6ef 100644 --- a/pkgs/dartpad_ui/lib/console.dart +++ b/pkgs/dartpad_ui/lib/console.dart @@ -52,62 +52,66 @@ class _ConsoleWidgetState extends State { return Container( decoration: BoxDecoration( color: theme.scaffoldBackgroundColor, - border: widget.showDivider - ? Border( - top: Divider.createBorderSide( - context, - width: 8.0, - color: theme.colorScheme.surface, - )) - : null, + border: + widget.showDivider + ? Border( + top: Divider.createBorderSide( + context, + width: 8.0, + color: theme.colorScheme.surface, + ), + ) + : null, ), padding: const EdgeInsets.all(denseSpacing), child: ValueListenableBuilder( valueListenable: widget.output, - builder: (context, consoleOutput, _) => Stack( - children: [ - SizedBox.expand( - child: SingleChildScrollView( - controller: scrollController, - child: SelectableText( - consoleOutput, - maxLines: null, - style: GoogleFonts.robotoMono( - fontSize: theme.textTheme.bodyMedium?.fontSize, + builder: + (context, consoleOutput, _) => Stack( + children: [ + SizedBox.expand( + child: SingleChildScrollView( + controller: scrollController, + child: SelectableText( + consoleOutput, + maxLines: null, + style: GoogleFonts.robotoMono( + fontSize: theme.textTheme.bodyMedium?.fontSize, + ), + ), ), ), - ), - ), - Padding( - padding: const EdgeInsets.all(denseSpacing), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (genAiEnabled && appModel.consoleShowingError) - MiniIconButton( - icon: Image.asset( - 'gemini_sparkle_192.png', - width: 16, - height: 16, + Padding( + padding: const EdgeInsets.all(denseSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (genAiEnabled && appModel.consoleShowingError) + MiniIconButton( + icon: Image.asset( + 'gemini_sparkle_192.png', + width: 16, + height: 16, + ), + tooltip: 'Suggest fix', + onPressed: + () => suggestFix( + context: context, + appType: appModel.appType, + errorMessage: consoleOutput, + ), + ), + MiniIconButton( + icon: const Icon(Icons.playlist_remove), + tooltip: 'Clear console', + onPressed: consoleOutput.isEmpty ? null : _clearConsole, ), - tooltip: 'Suggest fix', - onPressed: () => suggestFix( - context: context, - appType: appModel.appType, - errorMessage: consoleOutput, - ), - ), - MiniIconButton( - icon: const Icon(Icons.playlist_remove), - tooltip: 'Clear console', - onPressed: consoleOutput.isEmpty ? null : _clearConsole, + ], ), - ], - ), + ), + ], ), - ], - ), ), ); } diff --git a/pkgs/dartpad_ui/lib/editor/codemirror.dart b/pkgs/dartpad_ui/lib/editor/codemirror.dart index 389b1c4fe..5cc918184 100644 --- a/pkgs/dartpad_ui/lib/editor/codemirror.dart +++ b/pkgs/dartpad_ui/lib/editor/codemirror.dart @@ -91,10 +91,17 @@ extension type Doc._(JSObject _) implements JSObject { external void setSelection(Position position, [Position head]); external JSArray getAllMarks(); external TextMarker markText( - Position from, Position to, MarkTextOptions options); + Position from, + Position to, + MarkTextOptions options, + ); external int? indexFromPos(Position pos); - external void replaceRange(String replacement, Position from, - [Position? to, String? origin]); + external void replaceRange( + String replacement, + Position from, [ + Position? to, + String? origin, + ]); external Position posFromIndex(int index); } @@ -125,10 +132,7 @@ extension type MarkTextOptions._(JSObject _) implements JSObject { external String className; external String title; - external factory MarkTextOptions({ - String className, - String title, - }); + external factory MarkTextOptions({String className, String title}); } @anonymous @@ -181,7 +185,7 @@ extension type Vim._(JSObject _) implements JSObject { external void exitInsertMode(CodeMirror cm); void handleEsc(CodeMirror cm) => switch (cm.getKeymap()) { - 'vim-insert' => exitInsertMode(cm), - _ => _, - }; + 'vim-insert' => exitInsertMode(cm), + _ => _, + }; } diff --git a/pkgs/dartpad_ui/lib/editor/editor.dart b/pkgs/dartpad_ui/lib/editor/editor.dart index 319306b62..5df201e74 100644 --- a/pkgs/dartpad_ui/lib/editor/editor.dart +++ b/pkgs/dartpad_ui/lib/editor/editor.dart @@ -32,24 +32,28 @@ void _initViewFactory() { if (_viewFactoryInitialized) return; _viewFactoryInitialized = true; - ui_web.platformViewRegistry - .registerViewFactory(_viewType, _codeMirrorFactory); + ui_web.platformViewRegistry.registerViewFactory( + _viewType, + _codeMirrorFactory, + ); } web.Element _codeMirrorFactory(int viewId) { - final div = web.document.createElement('div') as web.HTMLDivElement - ..style.width = '100%' - ..style.height = '100%'; + final div = + web.document.createElement('div') as web.HTMLDivElement + ..style.width = '100%' + ..style.height = '100%'; codeMirrorInstance = CodeMirror( - div, - { - 'lineNumbers': true, - 'lineWrapping': true, - 'mode': 'dart', - 'theme': 'darkpad', - ...codeMirrorOptions, - }.jsify()); + div, + { + 'lineNumbers': true, + 'lineWrapping': true, + 'mode': 'dart', + 'theme': 'darkpad', + ...codeMirrorOptions, + }.jsify(), + ); CodeMirror.commands.goLineLeft = ((JSObject? _) => _handleGoLineLeft(codeMirrorInstance!)).toJS; @@ -78,11 +82,7 @@ class EditorWidget extends StatefulWidget { final AppModel appModel; final AppServices appServices; - EditorWidget({ - required this.appModel, - required this.appServices, - super.key, - }) { + EditorWidget({required this.appModel, required this.appServices, super.key}) { _initViewFactory(); } @@ -163,9 +163,9 @@ class _EditorWidgetState extends State implements EditorService { if (issue.location.line != -1) { codeMirror!.getDoc().setSelection( - Position(line: line, ch: column), - Position(line: line, ch: column + issue.location.charLength), - ); + Position(line: line, ch: column), + Position(line: line, ch: column + issue.location.charLength), + ); } else { codeMirror?.getDoc().setSelection(Position(line: 0, ch: 0)); } @@ -221,7 +221,9 @@ class _EditorWidgetState extends State implements EditorService { // Use a longer delay so that the platform view is displayed // correctly when compiled to Wasm Future.delayed( - const Duration(milliseconds: 80), () => codeMirror!.refresh()); + const Duration(milliseconds: 80), + () => codeMirror!.refresh(), + ); codeMirror!.on( 'change', @@ -245,38 +247,46 @@ class _EditorWidgetState extends State implements EditorService { ); appModel.sourceCodeController.addListener(_updateCodemirrorFromModel); - appModel.analysisIssues - .addListener(() => _updateIssues(appModel.analysisIssues.value)); + appModel.analysisIssues.addListener( + () => _updateIssues(appModel.analysisIssues.value), + ); appModel.vimKeymapsEnabled.addListener(_updateCodemirrorKeymap); widget.appServices.registerEditorService(this); - CodeMirror.commands.autocomplete = (CodeMirror codeMirror) { - _completions().then((completions) { - codeMirror.showHint( - HintOptions(hint: CodeMirror.hint.dart, results: completions)); - }); - return JSObject(); - }.toJS; + CodeMirror.commands.autocomplete = + (CodeMirror codeMirror) { + _completions().then((completions) { + codeMirror.showHint( + HintOptions(hint: CodeMirror.hint.dart, results: completions), + ); + }); + return JSObject(); + }.toJS; CodeMirror.registerHelper( - 'hint', - 'dart', - (CodeMirror editor, [HintOptions? options]) { - return options!.results; - }.toJS); + 'hint', + 'dart', + (CodeMirror editor, [HintOptions? options]) { + return options!.results; + }.toJS, + ); // Listen for document body to be visible, then force a code mirror refresh. final observer = web.IntersectionObserver( - (JSArray entries, - web.IntersectionObserver observer) { + ( + JSArray entries, + web.IntersectionObserver observer, + ) { for (final entry in entries.toDart) { if (entry.isIntersecting) { observer.unobserve(web.document.body!); // Use a longer delay so that the platform view is displayed // correctly when compiled to Wasm Future.delayed( - const Duration(milliseconds: 80), () => codeMirror!.refresh()); + const Duration(milliseconds: 80), + () => codeMirror!.refresh(), + ); return; } } @@ -314,8 +324,8 @@ class _EditorWidgetState extends State implements EditorService { child: HtmlElementView( key: _elementViewKey, viewType: _viewType, - onPlatformViewCreated: (id) => - _platformViewCreated(id, darkMode: darkMode), + onPlatformViewCreated: + (id) => _platformViewCreated(id, darkMode: darkMode), ), ); } @@ -327,8 +337,9 @@ class _EditorWidgetState extends State implements EditorService { widget.appServices.registerEditorService(null); - widget.appModel.sourceCodeController - .removeListener(_updateCodemirrorFromModel); + widget.appModel.sourceCodeController.removeListener( + _updateCodemirrorFromModel, + ); widget.appModel.appReady.removeListener(_updateEditableStatus); widget.appModel.vimKeymapsEnabled.removeListener(_updateCodemirrorKeymap); @@ -380,10 +391,7 @@ class _EditorWidgetState extends State implements EditorService { doc.markText( Position(line: line, ch: column), Position(line: line, ch: column + issue.location.charLength), - MarkTextOptions( - className: 'squiggle-$kind', - title: issue.message, - ), + MarkTextOptions(className: 'squiggle-$kind', title: issue.message), ); } } @@ -413,24 +421,27 @@ class _EditorWidgetState extends State implements EditorService { } return HintResults( - list: [ - ...response.fixes.map((change) => change.toHintResult(editor)), - ...response.assists.map((change) => change.toHintResult(editor)), - ].toJS, + list: + [ + ...response.fixes.map((change) => change.toHintResult(editor)), + ...response.assists.map((change) => change.toHintResult(editor)), + ].toJS, from: doc.posFromIndex(sourceOffset), to: doc.posFromIndex(0), ); } else { final response = await appServices.services .complete( - services.SourceRequest(source: source, offset: sourceOffset)) + services.SourceRequest(source: source, offset: sourceOffset), + ) .onError((error, st) => services.CompleteResponse.empty); final offset = response.replacementOffset; final length = response.replacementLength; - final hints = response.suggestions - .map((suggestion) => suggestion.toHintResult()) - .toList(); + final hints = + response.suggestions + .map((suggestion) => suggestion.toHintResult()) + .toList(); // Remove hints where both the replacement text and the display text are // the same. @@ -494,15 +505,10 @@ void _weHandleElsewhere(CodeMirror editor) { const codeMirrorOptions = { 'autoCloseBrackets': true, - 'autoCloseTags': { - 'whenOpening': true, - 'whenClosing': true, - }, + 'autoCloseTags': {'whenOpening': true, 'whenClosing': true}, 'autofocus': false, 'cursorHeight': 0.85, - 'continueComments': { - 'continueLineComment': false, - }, + 'continueComments': {'continueLineComment': false}, 'extraKeys': { 'Esc': '...', 'Esc Tab': false, @@ -525,32 +531,22 @@ const codeMirrorOptions = { 'Shift-Cmd-F': 'weHandleElsewhere', 'Cmd-Alt-F': false, }, - 'gutters': [ - 'CodeMirror-linenumbers', - ], + 'gutters': ['CodeMirror-linenumbers'], 'highlightSelectionMatches': { 'style': 'highlight-selection-matches', 'showToken': false, 'annotateScrollbar': true, }, - 'hintOptions': { - 'completeSingle': false, - }, + 'hintOptions': {'completeSingle': false}, 'indentUnit': 2, 'matchBrackets': true, - 'matchTags': { - 'bothTags': true, - }, + 'matchTags': {'bothTags': true}, 'tabSize': 2, 'viewportMargin': 100, 'scrollbarStyle': 'simple', }; -enum CompletionType { - auto, - manual, - quickfix, -} +enum CompletionType { auto, manual, quickfix } extension CompletionSuggestionExtension on services.CompletionSuggestion { HintResult toHintResult() { @@ -626,10 +622,7 @@ class _ReadOnlyEditorWidgetState extends State { Widget build(BuildContext context) { return SizedBox( height: 500, - child: EditorWidget( - appModel: _appModel, - appServices: _appServices, - ), + child: EditorWidget(appModel: _appModel, appServices: _appServices), ); } } diff --git a/pkgs/dartpad_ui/lib/embed.dart b/pkgs/dartpad_ui/lib/embed.dart index 129b9f05c..cc72e8f70 100644 --- a/pkgs/dartpad_ui/lib/embed.dart +++ b/pkgs/dartpad_ui/lib/embed.dart @@ -17,8 +17,10 @@ void handleEmbedMessage(AppServices services, {bool runOnInject = false}) { web.window.addEventListener( 'message', (web.MessageEvent event) { - if (event.data case _SourceCodeMessage(:final type?, :final sourceCode?) - when type == 'sourceCode') { + if (event.data case _SourceCodeMessage( + :final type?, + :final sourceCode?, + ) when type == 'sourceCode') { if (sourceCode.isNotEmpty) { services.appModel.sourceCodeController.text = sourceCode; if (runOnInject) { diff --git a/pkgs/dartpad_ui/lib/execution/execution.dart b/pkgs/dartpad_ui/lib/execution/execution.dart index ca3897ab1..1312fa725 100644 --- a/pkgs/dartpad_ui/lib/execution/execution.dart +++ b/pkgs/dartpad_ui/lib/execution/execution.dart @@ -28,15 +28,16 @@ void _initViewFactory() { web.Element _iFrameFactory(int viewId) { // 'allow-popups' allows plugins like url_launcher to open popups. - final frame = web.document.createElement('iframe') as web.HTMLIFrameElement - ..sandbox.add('allow-scripts') - ..sandbox.add('allow-popups') - ..sandbox.add('allow-popups-to-escape-sandbox') - ..allow += 'clipboard-write; ' - ..src = 'frame.html' - ..style.border = 'none' - ..style.width = '100%' - ..style.height = '100%'; + final frame = + web.document.createElement('iframe') as web.HTMLIFrameElement + ..sandbox.add('allow-scripts') + ..sandbox.add('allow-popups') + ..sandbox.add('allow-popups-to-escape-sandbox') + ..allow += 'clipboard-write; ' + ..src = 'frame.html' + ..style.border = 'none' + ..style.width = '100%' + ..style.height = '100%'; executionServiceInstance = ExecutionServiceImpl(frame); @@ -78,8 +79,9 @@ class _ExecutionWidgetState extends State { key: _elementViewKey, viewType: _viewType, onPlatformViewCreated: (int id) { - widget.appServices - .registerExecutionService(executionServiceInstance); + widget.appServices.registerExecutionService( + executionServiceInstance, + ); }, ), ); diff --git a/pkgs/dartpad_ui/lib/execution/frame.dart b/pkgs/dartpad_ui/lib/execution/frame.dart index cbee83423..03db84f8e 100644 --- a/pkgs/dartpad_ui/lib/execution/frame.dart +++ b/pkgs/dartpad_ui/lib/execution/frame.dart @@ -126,10 +126,12 @@ function contextLoaded() { }'''); if (isFlutter) { script.writeln( - 'require(["dart_sdk_new", "flutter_web_new", "ddc_module_loader"], contextLoaded);'); + 'require(["dart_sdk_new", "flutter_web_new", "ddc_module_loader"], contextLoaded);', + ); } else { script.writeln( - 'require(["dart_sdk_new", "ddc_module_loader"], contextLoaded);'); + 'require(["dart_sdk_new", "ddc_module_loader"], contextLoaded);', + ); } } else { // Redirect print messages to the host. @@ -213,10 +215,7 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) { Future _send(String command, Map params) { // TODO: Use dartpad.dev instead of '*'? _frame.contentWindowCrossOrigin?.postMessage( - { - 'command': command, - ...params, - }.jsify(), + {'command': command, ...params}.jsify(), '*'.toJS, ); return Future.value(); @@ -235,10 +234,12 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) { _frame = clone; } - return _readyCompleter.future.timeout(const Duration(seconds: 1), - onTimeout: () { - if (!_readyCompleter.isCompleted) _readyCompleter.complete(); - }); + return _readyCompleter.future.timeout( + const Duration(seconds: 1), + onTimeout: () { + if (!_readyCompleter.isCompleted) _readyCompleter.complete(); + }, + ); } void _initListener() { diff --git a/pkgs/dartpad_ui/lib/flutter_samples.dart b/pkgs/dartpad_ui/lib/flutter_samples.dart index 82c15fe72..10a42ad8a 100644 --- a/pkgs/dartpad_ui/lib/flutter_samples.dart +++ b/pkgs/dartpad_ui/lib/flutter_samples.dart @@ -14,16 +14,17 @@ class FlutterSampleLoader { // There are only two hosted versions of the docs: master/main and stable. final sampleUrl = switch (channel) { 'master' || - 'main' => - 'https://main-api.flutter.dev/snippets/$sampleId.dart', + 'main' => 'https://main-api.flutter.dev/snippets/$sampleId.dart', _ => 'https://api.flutter.dev/snippets/$sampleId.dart', }; final response = await client.get(Uri.parse(sampleUrl)); if (response.statusCode != 200) { - throw Exception('Unable to load sample ' - '(${response.statusCode} ${response.reasonPhrase}})'); + throw Exception( + 'Unable to load sample ' + '(${response.statusCode} ${response.reasonPhrase}})', + ); } return response.body; diff --git a/pkgs/dartpad_ui/lib/gists.dart b/pkgs/dartpad_ui/lib/gists.dart index c9b3b44e7..5d85ffbd3 100644 --- a/pkgs/dartpad_ui/lib/gists.dart +++ b/pkgs/dartpad_ui/lib/gists.dart @@ -11,12 +11,15 @@ class GistLoader { final http.Client client = http.Client(); Future load(String gistId) async { - final response = - await client.get(Uri.parse('https://api.github.com/gists/$gistId')); + final response = await client.get( + Uri.parse('https://api.github.com/gists/$gistId'), + ); if (response.statusCode != 200) { - throw Exception('Unable to load gist ' - '(${response.statusCode} ${response.reasonPhrase}})'); + throw Exception( + 'Unable to load gist ' + '(${response.statusCode} ${response.reasonPhrase}})', + ); } return Gist.fromJson(jsonDecode(response.body) as Map); @@ -46,7 +49,7 @@ class Gist { } factory Gist.fromJson(Map json) { -/* { + /* { "id": "d3bd83918d21b6d5f778bdc69c3d36d6", "description": "Fibonacci", "owner": { @@ -77,10 +80,11 @@ class Gist { id: json['id'] as String, description: json['description'] as String?, owner: owner['login'] as String?, - files: files.values - .cast>() - .map(GistFile.fromJson) - .toList(), + files: + files.values + .cast>() + .map(GistFile.fromJson) + .toList(), ); } @@ -98,14 +102,17 @@ class Gist { } void _validateGist() { - final file = - files.singleWhereOrNull((file) => file.fileName.endsWith('.dart')); + final file = files.singleWhereOrNull( + (file) => file.fileName.endsWith('.dart'), + ); if (file == null) { validationIssues.add('Warning: no Dart file found in the gist'); } else if (file.fileName != defaultFileName) { - validationIssues.add('Warning: no gist content in $defaultFileName ' - '(loading from ${file.fileName})'); + validationIssues.add( + 'Warning: no gist content in $defaultFileName ' + '(loading from ${file.fileName})', + ); } } } diff --git a/pkgs/dartpad_ui/lib/keys.dart b/pkgs/dartpad_ui/lib/keys.dart index d4fb0639a..56d3482ea 100644 --- a/pkgs/dartpad_ui/lib/keys.dart +++ b/pkgs/dartpad_ui/lib/keys.dart @@ -86,19 +86,12 @@ extension SingleActivatorExtension on SingleActivator { ), borderRadius: const BorderRadius.all(Radius.circular(4)), ), - padding: const EdgeInsets.symmetric( - vertical: 2, - horizontal: 6, - ), + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (shift) - const Icon( - Icons.arrow_upward, - size: 16, - color: subtleColor, - ), + const Icon(Icons.arrow_upward, size: 16, color: subtleColor), if (alt) Icon( _mac ? Icons.keyboard_option_key : Icons.keyboard_alt, diff --git a/pkgs/dartpad_ui/lib/main.dart b/pkgs/dartpad_ui/lib/main.dart index 5a4f52edf..69dbbcc54 100644 --- a/pkgs/dartpad_ui/lib/main.dart +++ b/pkgs/dartpad_ui/lib/main.dart @@ -46,9 +46,7 @@ void main() async { } class DartPadApp extends StatefulWidget { - const DartPadApp({ - super.key, - }); + const DartPadApp({super.key}); @override State createState() => _DartPadAppState(); @@ -58,14 +56,15 @@ class _DartPadAppState extends State { late final GoRouter router = GoRouter( initialLocation: '/', routes: [ - GoRoute( - path: '/', - builder: _homePageBuilder, - ), + GoRoute(path: '/', builder: _homePageBuilder), GoRoute( path: '/:gistId', - builder: (context, state) => _homePageBuilder(context, state, - gist: state.pathParameters['gistId']), + builder: + (context, state) => _homePageBuilder( + context, + state, + gist: state.pathParameters['gistId'], + ), ), ], ); @@ -120,8 +119,11 @@ class _DartPadAppState extends State { }); } - Widget _homePageBuilder(BuildContext context, GoRouterState state, - {String? gist}) { + Widget _homePageBuilder( + BuildContext context, + GoRouterState state, { + String? gist, + }) { final gistId = gist ?? state.uri.queryParameters['id']; final builtinSampleId = state.uri.queryParameters['sample']; final flutterSampleId = state.uri.queryParameters['sample_id']; @@ -158,9 +160,7 @@ class _DartPadAppState extends State { ), brightness: Brightness.light, dividerColor: lightDividerColor, - dividerTheme: const DividerThemeData( - color: lightDividerColor, - ), + dividerTheme: const DividerThemeData(color: lightDividerColor), scaffoldBackgroundColor: Colors.white, menuButtonTheme: MenuButtonThemeData( style: MenuItemButton.styleFrom( @@ -181,9 +181,7 @@ class _DartPadAppState extends State { ), brightness: Brightness.dark, dividerColor: darkDividerColor, - dividerTheme: const DividerThemeData( - color: darkDividerColor, - ), + dividerTheme: const DividerThemeData(color: darkDividerColor), textButtonTheme: const TextButtonThemeData( style: ButtonStyle( foregroundColor: WidgetStatePropertyAll(darkLinkButtonColor), @@ -218,10 +216,10 @@ class DartPadMainPage extends StatefulWidget { this.builtinSampleId, this.flutterSampleId, }) : super( - key: ValueKey( - 'sample:$builtinSampleId gist:$gistId flutter:$flutterSampleId', - ), - ); + key: ValueKey( + 'sample:$builtinSampleId gist:$gistId flutter:$flutterSampleId', + ), + ); @override State createState() => _DartPadMainPageState(); @@ -235,8 +233,9 @@ class _DartPadMainPageState extends State late final TabController tabController; final Key _executionWidgetKey = GlobalKey(debugLabel: 'execution-widget'); - final ValueKey _loadingOverlayKey = - const ValueKey('loading-overlay-widget'); + final ValueKey _loadingOverlayKey = const ValueKey( + 'loading-overlay-widget', + ); final ValueKey _editorKey = const ValueKey('editor'); final ValueKey _consoleKey = const ValueKey('console'); final ValueKey _tabBarKey = const ValueKey('tab-bar'); @@ -247,50 +246,47 @@ class _DartPadMainPageState extends State void initState() { super.initState(); - tabController = TabController(length: 2, vsync: this) - ..addListener( - () { - // Rebuild when the user changes tabs so that the IndexedStack updates - // its active child view. - setState(() {}); - }, - ); + tabController = TabController(length: 2, vsync: this)..addListener(() { + // Rebuild when the user changes tabs so that the IndexedStack updates + // its active child view. + setState(() {}); + }); final leftPanelSize = widget.embedMode ? 0.62 : 0.50; - mainSplitter = - SplitViewController(weights: [leftPanelSize, 1.0 - leftPanelSize]) - ..addListener(() { - appModel.splitDragStateManager.handleSplitChanged(); - }); + mainSplitter = SplitViewController( + weights: [leftPanelSize, 1.0 - leftPanelSize], + )..addListener(() { + appModel.splitDragStateManager.handleSplitChanged(); + }); - final channel = widget.initialChannel != null - ? Channel.forName(widget.initialChannel!) - : null; + final channel = + widget.initialChannel != null + ? Channel.forName(widget.initialChannel!) + : null; appModel = AppModel(); - appServices = AppServices( - appModel, - channel ?? Channel.defaultChannel, - ); + appServices = AppServices(appModel, channel ?? Channel.defaultChannel); appServices.populateVersions(); appServices .performInitialLoad( - gistId: widget.gistId, - sampleId: widget.builtinSampleId, - flutterSampleId: widget.flutterSampleId, - channel: widget.initialChannel, - keybinding: LocalStorage.instance.getUserKeybinding(), - getFallback: () => - LocalStorage.instance.getUserCode() ?? Samples.defaultSnippet(), - ) + gistId: widget.gistId, + sampleId: widget.builtinSampleId, + flutterSampleId: widget.flutterSampleId, + channel: widget.initialChannel, + keybinding: LocalStorage.instance.getUserKeybinding(), + getFallback: + () => + LocalStorage.instance.getUserCode() ?? + Samples.defaultSnippet(), + ) .then((value) { - // Start listening for inject code messages. - handleEmbedMessage(appServices, runOnInject: widget.runOnLoad); - if (widget.runOnLoad) { - appServices.performCompileAndRun(); - } - }); + // Start listening for inject code messages. + handleEmbedMessage(appServices, runOnInject: widget.runOnLoad); + if (widget.runOnLoad) { + appServices.performCompileAndRun(); + } + }); appModel.compilingState.addListener(_handleRunStarted); } @@ -330,10 +326,7 @@ class _DartPadMainPageState extends State final tabBar = TabBar( controller: tabController, - tabs: const [ - Tab(text: 'Code'), - Tab(text: 'Output'), - ], + tabs: const [Tab(text: 'Code'), Tab(text: 'Output')], // Remove the divider line at the bottom of the tab bar. dividerHeight: 0, key: _tabBarKey, @@ -348,8 +341,9 @@ class _DartPadMainPageState extends State return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final domHeight = mode.calcDomHeight(constraints.maxHeight); - final consoleHeight = - mode.calcConsoleHeight(constraints.maxHeight); + final consoleHeight = mode.calcConsoleHeight( + constraints.maxHeight, + ); return Column( children: [ @@ -372,69 +366,67 @@ class _DartPadMainPageState extends State ], ); - final scaffold = LayoutBuilder(builder: (context, constraints) { - // Use the mobile UI layout for small screen widths. - if (constraints.maxWidth <= smallScreenWidth) { - return Scaffold( - key: _scaffoldKey, - appBar: widget.embedMode - ? tabBar - : DartPadAppBar( - theme: theme, - appServices: appServices, - appModel: appModel, - widget: widget, - bottom: tabBar, - ), - body: Column( - children: [ - Expanded( - child: IndexedStack( - index: tabController.index, - children: [ - editor, - executionStack, - ], - ), - ), - if (!widget.embedMode) - const StatusLineWidget(mobileVersion: true), - ], - ), - ); - } else { - // Return the desktop UI. - return Scaffold( - key: _scaffoldKey, - appBar: widget.embedMode - ? null - : DartPadAppBar( - theme: theme, - appServices: appServices, - appModel: appModel, - widget: widget, + final scaffold = LayoutBuilder( + builder: (context, constraints) { + // Use the mobile UI layout for small screen widths. + if (constraints.maxWidth <= smallScreenWidth) { + return Scaffold( + key: _scaffoldKey, + appBar: + widget.embedMode + ? tabBar + : DartPadAppBar( + theme: theme, + appServices: appServices, + appModel: appModel, + widget: widget, + bottom: tabBar, + ), + body: Column( + children: [ + Expanded( + child: IndexedStack( + index: tabController.index, + children: [editor, executionStack], + ), ), - body: Column( - children: [ - Expanded( - child: SplitView( - viewMode: SplitViewMode.Horizontal, - gripColor: theme.colorScheme.surface, - gripColorActive: theme.colorScheme.surface, - gripSize: defaultGripSize, - controller: mainSplitter, - children: [ - editor, - executionStack, - ], + if (!widget.embedMode) + const StatusLineWidget(mobileVersion: true), + ], + ), + ); + } else { + // Return the desktop UI. + return Scaffold( + key: _scaffoldKey, + appBar: + widget.embedMode + ? null + : DartPadAppBar( + theme: theme, + appServices: appServices, + appModel: appModel, + widget: widget, + ), + body: Column( + children: [ + Expanded( + child: SplitView( + viewMode: SplitViewMode.Horizontal, + gripColor: theme.colorScheme.surface, + gripColorActive: theme.colorScheme.surface, + gripSize: defaultGripSize, + controller: mainSplitter, + children: [editor, executionStack], + ), ), - ), - if (!widget.embedMode) const StatusLineWidget(), - ], - ), - ); - } - }); + if (!widget.embedMode) const StatusLineWidget(), + ], + ), + ); + } + }, + ); return Provider.value( value: appServices, @@ -476,10 +468,7 @@ class _DartPadMainPageState extends State appServices.editorService?.showQuickFixes(); }, }, - child: Focus( - autofocus: true, - child: scaffold, - ), + child: Focus(autofocus: true, child: scaffold), ), ), ); @@ -522,10 +511,7 @@ class _DartPadMainPageState extends State } class LoadingOverlay extends StatelessWidget { - const LoadingOverlay({ - super.key, - required this.appModel, - }); + const LoadingOverlay({super.key, required this.appModel}); final AppModel appModel; @@ -544,9 +530,10 @@ class LoadingOverlay extends StatelessWidget { color: color.withValues(alpha: compiling ? 0.8 : 0), duration: animationDelay, curve: animationCurve, - child: compiling - ? const GoldenRatioCenter(child: CircularProgressIndicator()) - : const SizedBox(width: 1), + child: + compiling + ? const GoldenRatioCenter(child: CircularProgressIndicator()) + : const SizedBox(width: 1), ); }, ); @@ -571,76 +558,80 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return AppBar( - backgroundColor: theme.colorScheme.surface, - title: SizedBox( - height: toolbarItemHeight, - child: Row( - children: [ - const Logo(width: 32, type: 'dart'), - const SizedBox(width: denseSpacing), - Text(appName, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface)), - // Hide new snippet buttons when the screen width is too small. - if (constraints.maxWidth > smallScreenWidth) ...[ - const SizedBox(width: defaultSpacing * 4), - NewSnippetWidget(appServices: appServices), - const SizedBox(width: denseSpacing), - const ListSamplesWidget(), - ] else ...[ - const SizedBox(width: defaultSpacing), - NewSnippetWidget(appServices: appServices, smallIcon: true), - const SizedBox(width: defaultSpacing), - const ListSamplesWidget(smallIcon: true), - ], - - if (genAiEnabled) ...[ + return LayoutBuilder( + builder: (context, constraints) { + return AppBar( + backgroundColor: theme.colorScheme.surface, + title: SizedBox( + height: toolbarItemHeight, + child: Row( + children: [ + const Logo(width: 32, type: 'dart'), const SizedBox(width: denseSpacing), - GeminiMenu( - generateNewCode: () => _generateNewCode(context), - updateExistingCode: () => _updateExistingCode(context), + Text( + appName, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), ), - ], + // Hide new snippet buttons when the screen width is too small. + if (constraints.maxWidth > smallScreenWidth) ...[ + const SizedBox(width: defaultSpacing * 4), + NewSnippetWidget(appServices: appServices), + const SizedBox(width: denseSpacing), + const ListSamplesWidget(), + ] else ...[ + const SizedBox(width: defaultSpacing), + NewSnippetWidget(appServices: appServices, smallIcon: true), + const SizedBox(width: defaultSpacing), + const ListSamplesWidget(smallIcon: true), + ], - const SizedBox(width: defaultSpacing), - // Hide the snippet title when the screen width is too small. - if (constraints.maxWidth > smallScreenWidth) - Expanded( - child: Center( - child: ValueListenableBuilder( - valueListenable: appModel.title, - builder: (_, String value, __) => Text(value), + if (genAiEnabled) ...[ + const SizedBox(width: denseSpacing), + GeminiMenu( + generateNewCode: () => _generateNewCode(context), + updateExistingCode: () => _updateExistingCode(context), + ), + ], + + const SizedBox(width: defaultSpacing), + // Hide the snippet title when the screen width is too small. + if (constraints.maxWidth > smallScreenWidth) + Expanded( + child: Center( + child: ValueListenableBuilder( + valueListenable: appModel.title, + builder: (_, String value, __) => Text(value), + ), ), ), - ), - const SizedBox(width: defaultSpacing), - ], - ), - ), - bottom: bottom, - actions: [ - // Hide the Install SDK button when the screen width is too small. - if (constraints.maxWidth > smallScreenWidth) - ContinueInMenu( - openInIdx: _openInIDX, + const SizedBox(width: defaultSpacing), + ], ), - const SizedBox(width: denseSpacing), - _BrightnessButton( - handleBrightnessChange: widget.handleBrightnessChanged, ), - const OverflowMenu(), - ], - ); - }); + bottom: bottom, + actions: [ + // Hide the Install SDK button when the screen width is too small. + if (constraints.maxWidth > smallScreenWidth) + ContinueInMenu(openInIdx: _openInIDX), + const SizedBox(width: denseSpacing), + _BrightnessButton( + handleBrightnessChange: widget.handleBrightnessChanged, + ), + const OverflowMenu(), + ], + ); + }, + ); } @override // kToolbarHeight is set to 56.0 in the framework. - Size get preferredSize => bottom == null - ? const Size(double.infinity, 56.0) - : const Size(double.infinity, 112.0); + Size get preferredSize => + bottom == null + ? const Size(double.infinity, 56.0) + : const Size(double.infinity, 112.0); Future _openInIDX() async { final code = appModel.sourceCodeController.text; @@ -655,27 +646,29 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget { final lastPrompt = LocalStorage.instance.getLastCreateCodePrompt(); final promptResponse = await showDialog( context: context, - builder: (context) => PromptDialog( - title: 'Generate New Code', - hint: 'Describe the code you want to generate', - initialAppType: LocalStorage.instance.getLastCreateCodeAppType(), - flutterPromptButtons: { - 'to-do app': - 'Generate a Flutter to-do app with add, remove, and complete task functionality', - 'login screen': - 'Generate a Flutter login screen with email and password fields, validation, and a submit button', - 'tic-tac-toe': - 'Generate a Flutter tic-tac-toe game with two players, win detection, and a reset button', - if (lastPrompt != null) 'your last prompt': lastPrompt, - }, - dartPromptButtons: { - 'hello, world': 'Generate a Dart hello world program', - 'fibonacci': - 'Generate a Dart program that prints the first 10 numbers in the Fibonacci sequence', - 'factorial': 'Generate a Dart program that prints the factorial of 5', - if (lastPrompt != null) 'your last prompt': lastPrompt, - }, - ), + builder: + (context) => PromptDialog( + title: 'Generate New Code', + hint: 'Describe the code you want to generate', + initialAppType: LocalStorage.instance.getLastCreateCodeAppType(), + flutterPromptButtons: { + 'to-do app': + 'Generate a Flutter to-do app with add, remove, and complete task functionality', + 'login screen': + 'Generate a Flutter login screen with email and password fields, validation, and a submit button', + 'tic-tac-toe': + 'Generate a Flutter tic-tac-toe game with two players, win detection, and a reset button', + if (lastPrompt != null) 'your last prompt': lastPrompt, + }, + dartPromptButtons: { + 'hello, world': 'Generate a Dart hello world program', + 'fibonacci': + 'Generate a Dart program that prints the first 10 numbers in the Fibonacci sequence', + 'factorial': + 'Generate a Dart program that prints the factorial of 5', + if (lastPrompt != null) 'your last prompt': lastPrompt, + }, + ), ); if (!context.mounted || @@ -698,10 +691,11 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget { final generateResponse = await showDialog( context: context, - builder: (context) => GeneratingCodeDialog( - stream: stream, - title: 'Generating New Code', - ), + builder: + (context) => GeneratingCodeDialog( + stream: stream, + title: 'Generating New Code', + ), ); if (!context.mounted || @@ -725,26 +719,27 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget { final lastPrompt = LocalStorage.instance.getLastUpdateCodePrompt(); final promptResponse = await showDialog( context: context, - builder: (context) => PromptDialog( - title: 'Update Existing Code', - hint: 'Describe the updates you\'d like to make to the code', - initialAppType: appModel.appType, - flutterPromptButtons: { - 'pretty': - 'Make the app pretty by improving the visual design - add proper spacing, consistent typography, a pleasing color scheme, and ensure the overall layout follows Material Design principles', - 'fancy': - 'Make the app fancy by adding rounded corners where appropriate, subtle shadows and animations for interactivity; make tasteful use of gradients and images', - 'emoji': - 'Make the app use emojis by adding appropriate emoji icons and text', - if (lastPrompt != null) 'your last prompt': lastPrompt, - }, - dartPromptButtons: { - 'pretty': 'Make the app pretty', - 'fancy': 'Make the app fancy', - 'emoji': 'Make the app use emojis', - if (lastPrompt != null) 'your last prompt': lastPrompt, - }, - ), + builder: + (context) => PromptDialog( + title: 'Update Existing Code', + hint: 'Describe the updates you\'d like to make to the code', + initialAppType: appModel.appType, + flutterPromptButtons: { + 'pretty': + 'Make the app pretty by improving the visual design - add proper spacing, consistent typography, a pleasing color scheme, and ensure the overall layout follows Material Design principles', + 'fancy': + 'Make the app fancy by adding rounded corners where appropriate, subtle shadows and animations for interactivity; make tasteful use of gradients and images', + 'emoji': + 'Make the app use emojis by adding appropriate emoji icons and text', + if (lastPrompt != null) 'your last prompt': lastPrompt, + }, + dartPromptButtons: { + 'pretty': 'Make the app pretty', + 'fancy': 'Make the app fancy', + 'emoji': 'Make the app use emojis', + if (lastPrompt != null) 'your last prompt': lastPrompt, + }, + ), ); if (!context.mounted || @@ -768,11 +763,12 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget { final generateResponse = await showDialog( context: context, - builder: (context) => GeneratingCodeDialog( - stream: stream, - title: 'Updating Existing Code', - existingSource: source, - ), + builder: + (context) => GeneratingCodeDialog( + stream: stream, + title: 'Updating Existing Code', + existingSource: source, + ), ); if (!context.mounted || @@ -815,10 +811,7 @@ class EditorWithButtons extends StatelessWidget { child: SectionWidget( child: Stack( children: [ - EditorWidget( - appModel: appModel, - appServices: appServices, - ), + EditorWidget(appModel: appModel, appServices: appServices), Padding( padding: const EdgeInsets.symmetric( vertical: denseSpacing, @@ -865,21 +858,21 @@ class EditorWithButtons extends StatelessWidget { const SizedBox(width: defaultSpacing), // Run action ValueListenableBuilder( - valueListenable: appModel.showReload, - builder: (_, bool value, __) { - if (!value) return const SizedBox(); - return ValueListenableBuilder( - valueListenable: appModel.canReload, - builder: (_, bool value, __) { - return PointerInterceptor( - child: ReloadButton( - onPressed: - value ? onCompileAndReload : null, - ), - ); - }, - ); - }), + valueListenable: appModel.showReload, + builder: (_, bool value, __) { + if (!value) return const SizedBox(); + return ValueListenableBuilder( + valueListenable: appModel.canReload, + builder: (_, bool value, __) { + return PointerInterceptor( + child: ReloadButton( + onPressed: value ? onCompileAndReload : null, + ), + ); + }, + ); + }, + ), const SizedBox(width: defaultSpacing), // Run action ValueListenableBuilder( @@ -899,9 +892,7 @@ class EditorWithButtons extends StatelessWidget { Container( alignment: Alignment.bottomRight, padding: const EdgeInsets.all(denseSpacing), - child: StatusWidget( - status: appModel.editorStatus, - ), + child: StatusWidget(status: appModel.editorStatus), ), ], ), @@ -957,10 +948,7 @@ class EditorWithButtons extends StatelessWidget { } return MediumDialog( title: title, - child: DocsWidget( - appModel: appModel, - documentResponse: result, - ), + child: DocsWidget(appModel: appModel, documentResponse: result), ); }, ); @@ -978,10 +966,7 @@ class EditorWithButtons extends StatelessWidget { class StatusLineWidget extends StatelessWidget { final bool mobileVersion; - const StatusLineWidget({ - this.mobileVersion = false, - super.key, - }); + const StatusLineWidget({this.mobileVersion = false, super.key}); @override Widget build(BuildContext context) { @@ -990,9 +975,7 @@ class StatusLineWidget extends StatelessWidget { final appModel = Provider.of(context); return Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - ), + decoration: BoxDecoration(color: theme.colorScheme.surface), padding: const EdgeInsets.symmetric( vertical: denseSpacing, horizontal: defaultSpacing, @@ -1003,17 +986,19 @@ class StatusLineWidget extends StatelessWidget { message: 'Keyboard shortcuts', waitDuration: tooltipDelay, child: TextButton( - onPressed: () => showDialog( - context: context, - builder: (context) => MediumDialog( - title: 'Keyboard shortcuts', - smaller: true, - child: KeyBindingsTable( - bindings: keys.keyBindings, - appModel: appModel, + onPressed: + () => showDialog( + context: context, + builder: + (context) => MediumDialog( + title: 'Keyboard shortcuts', + smaller: true, + child: KeyBindingsTable( + bindings: keys.keyBindings, + appModel: appModel, + ), + ), ), - ), - ), child: Icon( Icons.keyboard, color: Theme.of(context).colorScheme.onPrimary, @@ -1105,16 +1090,8 @@ class NewSnippetWidget extends StatelessWidget { final bool smallIcon; static const _menuItems = [ - ( - label: 'Dart snippet', - icon: Logo(type: 'dart'), - kind: 'dart', - ), - ( - label: 'Flutter snippet', - icon: Logo(type: 'flutter'), - kind: 'flutter', - ), + (label: 'Dart snippet', icon: Logo(type: 'dart'), kind: 'dart'), + (label: 'Flutter snippet', icon: Logo(type: 'flutter'), kind: 'flutter'), ]; const NewSnippetWidget({ @@ -1150,7 +1127,7 @@ class NewSnippetWidget extends StatelessWidget { ), onPressed: () => appServices.resetTo(type: item.kind), ), - ) + ), ], ); } @@ -1186,22 +1163,20 @@ class ListSamplesWidget extends StatelessWidget { in Samples.categories.entries) ...[ MenuItemButton( onPressed: null, - child: Text( - category, - style: Theme.of(context).textTheme.bodyLarge, - ), + child: Text(category, style: Theme.of(context).textTheme.bodyLarge), ), for (final sample in samples) MenuItemButton( leadingIcon: Logo(type: sample.icon), - onPressed: () => - GoRouter.of(context).replaceQueryParam('sample', sample.id), + onPressed: + () => + GoRouter.of(context).replaceQueryParam('sample', sample.id), child: Padding( padding: const EdgeInsets.only(right: 32), child: Text(sample.name), ), ), - ] + ], ]; return menuItems.map((e) => PointerInterceptor(child: e)).toList(); @@ -1209,9 +1184,7 @@ class ListSamplesWidget extends StatelessWidget { } class SelectChannelWidget extends StatelessWidget { - const SelectChannelWidget({ - super.key, - }); + const SelectChannelWidget({super.key}); @override Widget build(BuildContext context) { @@ -1220,27 +1193,28 @@ class SelectChannelWidget extends StatelessWidget { return ValueListenableBuilder( valueListenable: appServices.channel, - builder: (context, Channel value, _) => MenuAnchor( - builder: (context, MenuController controller, Widget? child) { - return TextButton.icon( - onPressed: () => controller.toggleMenuState(), - icon: const Icon(Icons.tune, size: smallIconSize), - label: Text('${value.displayName} channel'), - ); - }, - menuChildren: [ - for (final channel in channels) - PointerInterceptor( - child: MenuItemButton( - onPressed: () => _onTap(context, channel), - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 32, 0), - child: Text('${channel.displayName} channel'), + builder: + (context, Channel value, _) => MenuAnchor( + builder: (context, MenuController controller, Widget? child) { + return TextButton.icon( + onPressed: () => controller.toggleMenuState(), + icon: const Icon(Icons.tune, size: smallIconSize), + label: Text('${value.displayName} channel'), + ); + }, + menuChildren: [ + for (final channel in channels) + PointerInterceptor( + child: MenuItemButton( + onPressed: () => _onTap(context, channel), + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 32, 0), + child: Text('${channel.displayName} channel'), + ), + ), ), - ), - ), - ], - ), + ], + ), ); } @@ -1263,13 +1237,10 @@ class OverflowMenu extends StatelessWidget { const OverflowMenu({super.key}); static const _menuItems = [ - ( - label: 'Install SDK', - uri: 'https://flutter.dev/get-started', - ), + (label: 'Install SDK', uri: 'https://flutter.dev/get-started'), ( label: 'Sharing guide', - uri: 'https://github.com/dart-lang/dart-pad/wiki/Sharing-Guide' + uri: 'https://github.com/dart-lang/dart-pad/wiki/Sharing-Guide', ), ]; @@ -1293,7 +1264,7 @@ class OverflowMenu extends StatelessWidget { child: Text(item.label), ), ), - ) + ), ], ); } @@ -1328,7 +1299,7 @@ class ContinueInMenu extends StatelessWidget { child: Text('IDX'), ), ), - ].map((widget) => PointerInterceptor(child: widget)) + ].map((widget) => PointerInterceptor(child: widget)), ], ); } @@ -1346,11 +1317,7 @@ class GeminiMenu extends StatelessWidget { @override Widget build(BuildContext context) { - final image = Image.asset( - 'gemini_sparkle_192.png', - width: 24, - height: 24, - ); + final image = Image.asset('gemini_sparkle_192.png', width: 24, height: 24); return MenuAnchor( builder: (context, MenuController controller, Widget? child) { @@ -1378,7 +1345,7 @@ class GeminiMenu extends StatelessWidget { child: Text('Update Code'), ), ), - ].map((widget) => PointerInterceptor(child: widget)) + ].map((widget) => PointerInterceptor(child: widget)), ], ); } @@ -1433,7 +1400,8 @@ class KeyBindingsTable extends StatelessWidget { } first = false; children.add( - (shortcut as SingleActivator).renderToWidget(context)); + (shortcut as SingleActivator).renderToWidget(context), + ); } return Row(children: children); }, @@ -1442,9 +1410,7 @@ class KeyBindingsTable extends StatelessWidget { ), ), const Divider(), - _VimModeSwitch( - appModel: appModel, - ), + _VimModeSwitch(appModel: appModel), ], ); } @@ -1453,10 +1419,7 @@ class KeyBindingsTable extends StatelessWidget { class VersionInfoWidget extends StatefulWidget { final ValueListenable versions; - const VersionInfoWidget( - this.versions, { - super.key, - }); + const VersionInfoWidget(this.versions, {super.key}); @override State createState() => _VersionInfoWidgetState(); @@ -1494,9 +1457,7 @@ class _VersionInfoWidgetState extends State { } class _BrightnessButton extends StatelessWidget { - const _BrightnessButton({ - required this.handleBrightnessChange, - }); + const _BrightnessButton({required this.handleBrightnessChange}); final void Function(BuildContext, bool) handleBrightnessChange; @@ -1507,9 +1468,10 @@ class _BrightnessButton extends StatelessWidget { preferBelow: true, message: 'Toggle brightness', child: IconButton( - icon: Theme.of(context).brightness == Brightness.light - ? const Icon(Icons.dark_mode_outlined) - : const Icon(Icons.light_mode_outlined), + icon: + Theme.of(context).brightness == Brightness.light + ? const Icon(Icons.dark_mode_outlined) + : const Icon(Icons.light_mode_outlined), onPressed: () { handleBrightnessChange(context, !isBright); }, @@ -1521,9 +1483,7 @@ class _BrightnessButton extends StatelessWidget { class _VimModeSwitch extends StatelessWidget { final AppModel appModel; - const _VimModeSwitch({ - required this.appModel, - }); + const _VimModeSwitch({required this.appModel}); @override Widget build(BuildContext context) { diff --git a/pkgs/dartpad_ui/lib/model.dart b/pkgs/dartpad_ui/lib/model.dart index 7ff7022bc..7e64cb0de 100644 --- a/pkgs/dartpad_ui/lib/model.dart +++ b/pkgs/dartpad_ui/lib/model.dart @@ -56,8 +56,9 @@ class AppModel { final ValueNotifier consoleOutput = ValueNotifier(''); final ValueNotifier formattingBusy = ValueNotifier(false); - final ValueNotifier compilingState = - ValueNotifier(CompilingState.none); + final ValueNotifier compilingState = ValueNotifier( + CompilingState.none, + ); final ValueNotifier docHelpBusy = ValueNotifier(false); final ValueNotifier hasRun = ValueNotifier(false); @@ -70,8 +71,9 @@ class AppModel { final ValueNotifier _layoutMode = ValueNotifier(LayoutMode.both); ValueListenable get layoutMode => _layoutMode; - final ValueNotifier splitViewDragState = - ValueNotifier(SplitDragState.inactive); + final ValueNotifier splitViewDragState = ValueNotifier( + SplitDragState.inactive, + ); final SplitDragStateManager splitDragStateManager = SplitDragStateManager(); late final StreamSubscription _splitSubscription; @@ -87,9 +89,11 @@ class AppModel { AppModel() { consoleOutput.addListener(_recalcLayout); - void updateCanReload() => canReload.value = hasRun.value && - !compilingState.value.busy && - currentDeltaDill.value != null; + void updateCanReload() => + canReload.value = + hasRun.value && + !compilingState.value.busy && + currentDeltaDill.value != null; hasRun.addListener(updateCanReload); compilingState.addListener(updateCanReload); currentDeltaDill.addListener(updateCanReload); @@ -101,8 +105,9 @@ class AppModel { useNewDDC.addListener(updateShowReload); _appIsFlutter.addListener(updateShowReload); - _splitSubscription = - splitDragStateManager.onSplitDragUpdated.listen((SplitDragState value) { + _splitSubscription = splitDragStateManager.onSplitDragUpdated.listen(( + SplitDragState value, + ) { splitViewDragState.value = value; }); } @@ -203,8 +208,9 @@ class AppServices { appModel.analysisIssues.addListener(_updateEditorProblemsStatus); void updateUseNewDDC() { - appModel.useNewDDC.value = - _hotReloadableChannels.contains(_channel.value); + appModel.useNewDDC.value = _hotReloadableChannels.contains( + _channel.value, + ); } updateUseNewDDC(); @@ -225,8 +231,9 @@ class AppServices { void resetTo({String? type}) { type ??= 'dart'; - final source = - Samples.defaultSnippet(forFlutter: type.toLowerCase() == 'flutter'); + final source = Samples.defaultSnippet( + forFlutter: type.toLowerCase() == 'flutter', + ); // Reset the source. appModel.sourceCodeController.text = source; @@ -278,8 +285,9 @@ class AppServices { if (flutterSampleId != null) { final loader = FlutterSampleLoader(); - final progress = - appModel.editorStatus.showMessage(initialText: 'Loading…'); + final progress = appModel.editorStatus.showMessage( + initialText: 'Loading…', + ); try { final sample = await loader.loadFlutterSample( sampleId: flutterSampleId, @@ -308,8 +316,9 @@ class AppServices { if (gistId != null) { final gistLoader = GistLoader(); - final progress = - appModel.editorStatus.showMessage(initialText: 'Loading…'); + final progress = appModel.editorStatus.showMessage( + initialText: 'Loading…', + ); try { final gist = await gistLoader.load(gistId); progress.close(); @@ -363,16 +372,21 @@ class AppServices { final willUseReload = reload && appModel.useNewDDC.value; final source = appModel.sourceCodeController.text; - final progress = appModel.editorStatus - .showMessage(initialText: willUseReload ? 'Reloading…' : 'Compiling…'); + final progress = appModel.editorStatus.showMessage( + initialText: willUseReload ? 'Reloading…' : 'Compiling…', + ); try { CompileDDCResponse response; if (!appModel.useNewDDC.value) { response = await _compileDDC(CompileRequest(source: source)); } else if (reload) { - response = await _compileNewDDCReload(CompileRequest( - source: source, deltaDill: appModel.currentDeltaDill.value!)); + response = await _compileNewDDCReload( + CompileRequest( + source: source, + deltaDill: appModel.currentDeltaDill.value!, + ), + ); } else { response = await _compileNewDDC(CompileRequest(source: source)); } @@ -475,7 +489,8 @@ class AppServices { } Future _compileNewDDCReload( - CompileRequest request) async { + CompileRequest request, + ) async { try { appModel.compilingState.value = CompilingState.reloading; return await services.compileNewDDCReload(request); @@ -493,8 +508,9 @@ class AppServices { // register the new if (_executionService != null) { - stdoutSub = - _executionService!.onStdout.listen(appModel.appendLineToConsole); + stdoutSub = _executionService!.onStdout.listen( + appModel.appendLineToConsole, + ); } } @@ -557,8 +573,10 @@ class AppServices { } else { final message = '${issues.length} ${pluralize('issue', issues.length)}'; if (progress == null) { - appModel.editorStatus - .showMessage(initialText: message, name: 'problems'); + appModel.editorStatus.showMessage( + initialText: message, + name: 'problems', + ); } else { progress.updateText(message); } @@ -605,12 +623,15 @@ class SplitDragStateManager { StreamController.broadcast(); late final Stream onSplitDragUpdated; - SplitDragStateManager( - {Duration timeout = const Duration(milliseconds: 100)}) { - onSplitDragUpdated = _splitDragStateController.stream.timeout(timeout, - onTimeout: (eventSink) { - eventSink.add(SplitDragState.inactive); - }); + SplitDragStateManager({ + Duration timeout = const Duration(milliseconds: 100), + }) { + onSplitDragUpdated = _splitDragStateController.stream.timeout( + timeout, + onTimeout: (eventSink) { + eventSink.add(SplitDragState.inactive); + }, + ); } void handleSplitChanged() { diff --git a/pkgs/dartpad_ui/lib/problems.dart b/pkgs/dartpad_ui/lib/problems.dart index 7d654e3ef..2c0e6bc3f 100644 --- a/pkgs/dartpad_ui/lib/problems.dart +++ b/pkgs/dartpad_ui/lib/problems.dart @@ -21,10 +21,7 @@ const _rowPadding = 2.0; class ProblemsTableWidget extends StatelessWidget { final List problems; - const ProblemsTableWidget({ - required this.problems, - super.key, - }); + const ProblemsTableWidget({required this.problems, super.key}); @override Widget build(BuildContext context) { @@ -37,7 +34,8 @@ class ProblemsTableWidget extends StatelessWidget { var height = 0.0; // ignore: prefer_is_empty if (problems.length > 0) { - height = lineHeight * math.min(problems.length, visibleIssues) + + height = + lineHeight * math.min(problems.length, visibleIssues) + 1 + denseSpacing * 2; } @@ -47,9 +45,7 @@ class ProblemsTableWidget extends StatelessWidget { duration: animationDelay, curve: animationCurve, child: Container( - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - ), + decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest), padding: const EdgeInsets.all(denseSpacing), child: ListView.builder( itemCount: problems.length, @@ -67,10 +63,7 @@ class ProblemWidget extends StatelessWidget { final MenuController _menuController = MenuController(); final AnalysisIssue issue; - ProblemWidget({ - required this.issue, - super.key, - }); + ProblemWidget({required this.issue, super.key}); @override Widget build(BuildContext context) { @@ -86,7 +79,8 @@ class ProblemWidget extends StatelessWidget { issue.errorIcon, size: smallIconSize, color: issue.colorFor( - darkMode: colorScheme.brightness == Brightness.dark), + darkMode: colorScheme.brightness == Brightness.dark, + ), ), const SizedBox(width: denseSpacing), Expanded( @@ -111,13 +105,14 @@ class ProblemWidget extends StatelessWidget { ), if (genAiEnabled) ...[ IconButton( - onPressed: () => suggestFix( - context: context, - appType: appModel.appType, - errorMessage: issue.message, - line: issue.location.line, - column: issue.location.column, - ), + onPressed: + () => suggestFix( + context: context, + appType: appModel.appType, + errorMessage: issue.message, + line: issue.location.line, + column: issue.location.column, + ), tooltip: 'Suggest fix', icon: Image.asset( 'gemini_sparkle_192.png', @@ -197,14 +192,16 @@ class ProblemWidget extends StatelessWidget { void _quickFixes(BuildContext context) { final appServices = Provider.of(context, listen: false); - appServices.editorService?.jumpTo(AnalysisIssue( - kind: issue.kind, - message: issue.message, - location: Location( - line: issue.location.line, - column: issue.location.column, + appServices.editorService?.jumpTo( + AnalysisIssue( + kind: issue.kind, + message: issue.message, + location: Location( + line: issue.location.line, + column: issue.location.column, + ), ), - )); + ); appServices.editorService?.showQuickFixes(); } @@ -212,16 +209,16 @@ class ProblemWidget extends StatelessWidget { extension AnalysisIssueExtension on AnalysisIssue { Color colorFor({bool darkMode = true}) => switch (kind) { - 'error' => darkMode ? darkErrorColor : lightErrorColor, - 'warning' => darkMode ? darkWarningColor : lightWarningColor, - 'info' => darkMode ? darkInfoColor : lightInfoColor, - _ => darkMode ? darkIssueColor : lightIssueColor - }; + 'error' => darkMode ? darkErrorColor : lightErrorColor, + 'warning' => darkMode ? darkWarningColor : lightWarningColor, + 'info' => darkMode ? darkInfoColor : lightInfoColor, + _ => darkMode ? darkIssueColor : lightIssueColor, + }; IconData get errorIcon => switch (kind) { - 'error' => Icons.error_outline, - 'warning' => Icons.warning_outlined, - 'info' => Icons.info_outline, - _ => Icons.error_outline - }; + 'error' => Icons.error_outline, + 'warning' => Icons.warning_outlined, + 'info' => Icons.info_outline, + _ => Icons.error_outline, + }; } diff --git a/pkgs/dartpad_ui/lib/samples.g.dart b/pkgs/dartpad_ui/lib/samples.g.dart index 72effebf0..1546015ee 100644 --- a/pkgs/dartpad_ui/lib/samples.g.dart +++ b/pkgs/dartpad_ui/lib/samples.g.dart @@ -42,18 +42,9 @@ abstract final class Samples { ]; static const Map> categories = { - 'Dart': [ - _fibonacci, - _helloWorld, - ], - 'Flutter': [ - _counter, - _sunflower, - ], - 'Ecosystem': [ - _flameGame, - _googleSdk, - ], + 'Dart': [_fibonacci, _helloWorld], + 'Flutter': [_counter, _sunflower], + 'Ecosystem': [_flameGame, _googleSdk], }; static Sample? getById(String? id) => all.firstWhereOrNull((s) => s.id == id); @@ -128,11 +119,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, - home: Scaffold( - body: Center( - child: Text('Hello, World!'), - ), - ), + home: Scaffold(body: Center(child: Text('Hello, World!'))), ); } } @@ -193,10 +180,7 @@ class _GameAppState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Color(0xffa9d6e5), - Color(0xfff2e8cf), - ], + colors: [Color(0xffa9d6e5), Color(0xfff2e8cf)], ), ), child: SafeArea( @@ -207,9 +191,7 @@ class _GameAppState extends State { child: SizedBox( width: gameWidth, height: gameHeight, - child: GameWidget( - game: game, - ), + child: GameWidget(game: game), ), ), ), @@ -224,9 +206,12 @@ class _GameAppState extends State { class BrickBreaker extends FlameGame with HasCollisionDetection, KeyboardEvents, TapDetector { BrickBreaker() - : super( - camera: CameraComponent.withFixedResolution( - width: gameWidth, height: gameHeight)); + : super( + camera: CameraComponent.withFixedResolution( + width: gameWidth, + height: gameHeight, + ), + ); final rand = math.Random(); double get width => size.x; @@ -245,20 +230,27 @@ class BrickBreaker extends FlameGame world.removeAll(world.children.query()); world.removeAll(world.children.query()); - world.add(Ball( - difficultyModifier: difficultyModifier, - radius: ballRadius, - position: size / 2, - velocity: - Vector2((rand.nextDouble() - 0.5) * width, height * 0.3).normalized() - ..scale(height / 4), - )); - - world.add(Paddle( - size: Vector2(paddleWidth, paddleHeight), - cornerRadius: const Radius.circular(ballRadius / 2), - position: Vector2(width / 2, height * 0.95), - )); + world.add( + Ball( + difficultyModifier: difficultyModifier, + radius: ballRadius, + position: size / 2, + velocity: + Vector2( + (rand.nextDouble() - 0.5) * width, + height * 0.3, + ).normalized() + ..scale(height / 4), + ), + ); + + world.add( + Paddle( + size: Vector2(paddleWidth, paddleHeight), + cornerRadius: const Radius.circular(ballRadius / 2), + position: Vector2(width / 2, height * 0.95), + ), + ); world.addAll([ for (var i = 0; i < brickColors.length; i++) @@ -305,12 +297,14 @@ class Ball extends CircleComponent required double radius, required this.difficultyModifier, }) : super( - radius: radius, - anchor: Anchor.center, - paint: Paint() - ..color = const Color(0xff1e6091) - ..style = PaintingStyle.fill, - children: [CircleHitbox()]); + radius: radius, + anchor: Anchor.center, + paint: + Paint() + ..color = const Color(0xff1e6091) + ..style = PaintingStyle.fill, + children: [CircleHitbox()], + ); final Vector2 velocity; final double difficultyModifier; @@ -323,7 +317,9 @@ class Ball extends CircleComponent @override void onCollisionStart( - Set intersectionPoints, PositionComponent other) { + Set intersectionPoints, + PositionComponent other, + ) { super.onCollisionStart(intersectionPoints, other); if (other is PlayArea) { if (intersectionPoints.first.y <= 0) { @@ -333,16 +329,19 @@ class Ball extends CircleComponent } else if (intersectionPoints.first.x >= game.width) { velocity.x = -velocity.x; } else if (intersectionPoints.first.y >= game.height) { - add(RemoveEffect( - delay: 0.35, - onComplete: () { - game.startGame(); - }, - )); + add( + RemoveEffect( + delay: 0.35, + onComplete: () { + game.startGame(); + }, + ), + ); } } else if (other is Paddle) { velocity.y = -velocity.y; - velocity.x = velocity.x + + velocity.x = + velocity.x + (position.x - other.position.x) / other.size.x * game.width * 0.3; } else if (other is Brick) { if (position.y < other.position.y - other.size.y / 2) { @@ -369,9 +368,10 @@ class Paddle extends PositionComponent final Radius cornerRadius; - final _paint = Paint() - ..color = const Color(0xff1e6091) - ..style = PaintingStyle.fill; + final _paint = + Paint() + ..color = const Color(0xff1e6091) + ..style = PaintingStyle.fill; @override void update(double dt) { @@ -380,12 +380,16 @@ class Paddle extends PositionComponent final keysPressed = HardwareKeyboard.instance.logicalKeysPressed; if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) || keysPressed.contains(LogicalKeyboardKey.keyA)) { - position.x = - (position.x - (dt * 500)).clamp(width / 2, game.width - width / 2); + position.x = (position.x - (dt * 500)).clamp( + width / 2, + game.width - width / 2, + ); } else if (keysPressed.contains(LogicalKeyboardKey.arrowRight) || keysPressed.contains(LogicalKeyboardKey.keyD)) { - position.x = - (position.x + (dt * 500)).clamp(width / 2, game.width - width / 2); + position.x = (position.x + (dt * 500)).clamp( + width / 2, + game.width - width / 2, + ); } } @@ -393,10 +397,7 @@ class Paddle extends PositionComponent void render(Canvas canvas) { super.render(canvas); canvas.drawRRect( - RRect.fromRectAndRadius( - Offset.zero & size.toSize(), - cornerRadius, - ), + RRect.fromRectAndRadius(Offset.zero & size.toSize(), cornerRadius), _paint, ); } @@ -405,27 +406,32 @@ class Paddle extends PositionComponent void onDragUpdate(DragUpdateEvent event) { if (isRemoved) return; super.onDragUpdate(event); - position.x = (position.x + event.localDelta.x) - .clamp(width / 2, game.width - width / 2); + position.x = (position.x + event.localDelta.x).clamp( + width / 2, + game.width - width / 2, + ); } } class Brick extends RectangleComponent with CollisionCallbacks, HasGameReference { Brick(Vector2 position, Color color) - : super( - position: position, - size: Vector2(brickWidth, brickHeight), - anchor: Anchor.center, - paint: Paint() - ..color = color - ..style = PaintingStyle.fill, - children: [RectangleHitbox()], - ); + : super( + position: position, + size: Vector2(brickWidth, brickHeight), + anchor: Anchor.center, + paint: + Paint() + ..color = color + ..style = PaintingStyle.fill, + children: [RectangleHitbox()], + ); @override void onCollisionStart( - Set intersectionPoints, PositionComponent other) { + Set intersectionPoints, + PositionComponent other, + ) { super.onCollisionStart(intersectionPoints, other); removeFromParent(); @@ -522,14 +528,14 @@ class _ChatScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: switch (apiKey) { final providedKey? => ChatWidget(apiKey: providedKey), - _ => ApiKeyWidget(onSubmitted: (key) { + _ => ApiKeyWidget( + onSubmitted: (key) { setState(() => apiKey = key); - }), + }, + ), }, ); } @@ -558,18 +564,21 @@ class ApiKeyWidget extends StatelessWidget { Link( uri: Uri.https('makersuite.google.com', '/app/apikey'), target: LinkTarget.blank, - builder: (context, followLink) => TextButton( - onPressed: followLink, - child: const Text('Get an API Key'), - ), + builder: + (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('Get an API Key'), + ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( - decoration: - textFieldDecoration(context, 'Enter your API key'), + decoration: textFieldDecoration( + context, + 'Enter your API key', + ), controller: _textController, onSubmitted: (value) { onSubmitted(value); @@ -612,10 +621,7 @@ class _ChatWidgetState extends State { @override void initState() { super.initState(); - _model = GenerativeModel( - model: 'gemini-pro', - apiKey: widget.apiKey, - ); + _model = GenerativeModel(model: 'gemini-pro', apiKey: widget.apiKey); _chat = _model.startChat(); } @@ -623,9 +629,7 @@ class _ChatWidgetState extends State { WidgetsBinding.instance.addPostFrameCallback( (_) => _scrollController.animateTo( _scrollController.position.maxScrollExtent, - duration: const Duration( - milliseconds: 750, - ), + duration: const Duration(milliseconds: 750), curve: Curves.easeOutCirc, ), ); @@ -658,18 +662,17 @@ class _ChatWidgetState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, - horizontal: 15, - ), + padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15), child: Row( children: [ Expanded( child: TextField( autofocus: true, focusNode: _textFieldFocus, - decoration: - textFieldDecoration(context, 'Enter a prompt...'), + decoration: textFieldDecoration( + context, + 'Enter a prompt...', + ), controller: _textController, onSubmitted: (String value) { _sendChatMessage(value); @@ -703,9 +706,7 @@ class _ChatWidgetState extends State { }); try { - final response = await _chat.sendMessage( - Content.text(message), - ); + final response = await _chat.sendMessage(Content.text(message)); final text = response.text; if (text == null) { @@ -737,16 +738,14 @@ class _ChatWidgetState extends State { builder: (context) { return AlertDialog( title: const Text('Something went wrong'), - content: SingleChildScrollView( - child: Text(message), - ), + content: SingleChildScrollView(child: Text(message)), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('OK'), - ) + ), ], ); }, @@ -774,15 +773,13 @@ class MessageWidget extends StatelessWidget { child: Container( constraints: const BoxConstraints(maxWidth: 480), decoration: BoxDecoration( - color: isFromUser - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surfaceContainerHighest, + color: + isFromUser + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(18), ), - padding: const EdgeInsets.symmetric( - vertical: 15, - horizontal: 20, - ), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), margin: const EdgeInsets.only(bottom: 8), child: MarkdownBody(data: text), ), @@ -797,20 +794,12 @@ InputDecoration textFieldDecoration(BuildContext context, String hintText) => contentPadding: const EdgeInsets.all(15), hintText: hintText, border: OutlineInputBorder( - borderRadius: const BorderRadius.all( - Radius.circular(14), - ), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.secondary, - ), + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), ), focusedBorder: OutlineInputBorder( - borderRadius: const BorderRadius.all( - Radius.circular(14), - ), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.secondary, - ), + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), ), ); ''', @@ -834,9 +823,7 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, - theme: ThemeData( - colorSchemeSeed: Colors.blue, - ), + theme: ThemeData(colorSchemeSeed: Colors.blue), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } @@ -845,10 +832,7 @@ class MyApp extends StatelessWidget { class MyHomePage extends StatefulWidget { final String title; - const MyHomePage({ - super.key, - required this.title, - }); + const MyHomePage({super.key, required this.title}); @override State createState() => _MyHomePageState(); @@ -866,16 +850,12 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - 'You have pushed the button this many times:', - ), + const Text('You have pushed the button this many times:'), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, @@ -931,16 +911,12 @@ class _SunflowerState extends State { ), debugShowCheckedModeBanner: false, home: Scaffold( - appBar: AppBar( - title: const Text('Sunflower'), - ), + appBar: AppBar(title: const Text('Sunflower')), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded( - child: SunflowerWidget(seeds), - ), + Expanded(child: SunflowerWidget(seeds)), const SizedBox(height: 20), Text('Showing ${seeds.round()} seeds'), SizedBox( @@ -982,26 +958,30 @@ class SunflowerWidget extends StatelessWidget { final theta = i * tau / phi; final r = math.sqrt(i) * scaleFactor; - seedWidgets.add(AnimatedAlign( - key: ValueKey(i), - duration: Duration(milliseconds: rng.nextInt(500) + 250), - curve: Curves.easeInOut, - alignment: Alignment(r * math.cos(theta), -1 * r * math.sin(theta)), - child: const Dot(true), - )); + seedWidgets.add( + AnimatedAlign( + key: ValueKey(i), + duration: Duration(milliseconds: rng.nextInt(500) + 250), + curve: Curves.easeInOut, + alignment: Alignment(r * math.cos(theta), -1 * r * math.sin(theta)), + child: const Dot(true), + ), + ); } for (var j = seeds; j < maxSeeds; j++) { final x = math.cos(tau * j / (maxSeeds - 1)) * 0.9; final y = math.sin(tau * j / (maxSeeds - 1)) * 0.9; - seedWidgets.add(AnimatedAlign( - key: ValueKey(j), - duration: Duration(milliseconds: rng.nextInt(500) + 250), - curve: Curves.easeInOut, - alignment: Alignment(x, y), - child: const Dot(false), - )); + seedWidgets.add( + AnimatedAlign( + key: ValueKey(j), + duration: Duration(milliseconds: rng.nextInt(500) + 250), + curve: Curves.easeInOut, + alignment: Alignment(x, y), + child: const Dot(false), + ), + ); } return FittedBox( @@ -1030,10 +1010,7 @@ class Dot extends StatelessWidget { color: lit ? Colors.orange : Colors.grey.shade700, borderRadius: BorderRadius.circular(radius), ), - child: const SizedBox( - height: size, - width: size, - ), + child: const SizedBox(height: size, width: size), ); } } diff --git a/pkgs/dartpad_ui/lib/suggest_fix.dart b/pkgs/dartpad_ui/lib/suggest_fix.dart index 197484acc..975f21340 100644 --- a/pkgs/dartpad_ui/lib/suggest_fix.dart +++ b/pkgs/dartpad_ui/lib/suggest_fix.dart @@ -36,11 +36,12 @@ Future suggestFix({ final result = await showDialog( context: context, - builder: (context) => GeneratingCodeDialog( - stream: stream, - title: 'Generating Fix Suggestion', - existingSource: existingSource, - ), + builder: + (context) => GeneratingCodeDialog( + stream: stream, + title: 'Generating Fix Suggestion', + existingSource: existingSource, + ), ); if (!context.mounted || result == null || result.isEmpty) return; diff --git a/pkgs/dartpad_ui/lib/utils.dart b/pkgs/dartpad_ui/lib/utils.dart index 9071cb826..eaee88591 100644 --- a/pkgs/dartpad_ui/lib/utils.dart +++ b/pkgs/dartpad_ui/lib/utils.dart @@ -24,8 +24,9 @@ RelativeRect calculatePopupMenuPosition( }) { final render = context.findRenderObject() as RenderBox; final size = render.size; - final offset = - render.localToGlobal(Offset(0, growUpwards ? -size.height : size.height)); + final offset = render.localToGlobal( + Offset(0, growUpwards ? -size.height : size.height), + ); return RelativeRect.fromLTRB( offset.dx, @@ -104,8 +105,9 @@ class StatusController { return message; } - final ValueNotifier _state = - ValueNotifier(MessageStatus.empty); + final ValueNotifier _state = ValueNotifier( + MessageStatus.empty, + ); ValueListenable get state => _state; @@ -147,8 +149,8 @@ class Message { MessageState _state = MessageState.opening; Message._(StatusController parent, String message, {this.name}) - : _parent = parent, - _message = message; + : _parent = parent, + _message = message; MessageState get state => _state; @@ -163,8 +165,10 @@ class Message { } class MessageStatus { - static final MessageStatus empty = - MessageStatus(message: '', state: MessageState.closing); + static final MessageStatus empty = MessageStatus( + message: '', + state: MessageState.closing, + ); final String message; final MessageState state; @@ -184,11 +188,7 @@ class MessageStatus { String toString() => '[$state] $message'; } -enum MessageState { - opening, - showing, - closing; -} +enum MessageState { opening, showing, closing } extension StringUtils on String { String? get nullIfEmpty => isEmpty ? null : this; diff --git a/pkgs/dartpad_ui/lib/versions.dart b/pkgs/dartpad_ui/lib/versions.dart index 88c39e94c..731a2acd9 100644 --- a/pkgs/dartpad_ui/lib/versions.dart +++ b/pkgs/dartpad_ui/lib/versions.dart @@ -11,16 +11,14 @@ import 'theme.dart'; class VersionTable extends StatelessWidget { final VersionResponse version; - const VersionTable({ - required this.version, - super.key, - }); + const VersionTable({required this.version, super.key}); @override Widget build(BuildContext context) { final packages = version.packages.where((p) => p.supported).toList(); - var versionText = 'Based on Dart SDK ${version.dartVersion} ' + var versionText = + 'Based on Dart SDK ${version.dartVersion} ' 'and Flutter SDK ${version.flutterVersion}'; final experiments = version.experiments.join(', '); if (experiments.isNotEmpty) { diff --git a/pkgs/dartpad_ui/lib/widgets.dart b/pkgs/dartpad_ui/lib/widgets.dart index 033b53dc4..b5cb02331 100644 --- a/pkgs/dartpad_ui/lib/widgets.dart +++ b/pkgs/dartpad_ui/lib/widgets.dart @@ -24,12 +24,7 @@ class Hyperlink extends StatefulWidget { final String? displayText; final TextStyle? style; - const Hyperlink({ - required this.url, - this.displayText, - this.style, - super.key, - }); + const Hyperlink({required this.url, this.displayText, this.style, super.key}); @override State createState() => _HyperlinkState(); @@ -105,26 +100,18 @@ class MiniIconButton extends StatelessWidget { class RunButton extends ActionButton { const RunButton({super.key, super.onPressed}) - : super( - text: 'Run', - icon: const Icon( - Icons.play_arrow, - color: Colors.black, - size: 20, - ), - ); + : super( + text: 'Run', + icon: const Icon(Icons.play_arrow, color: Colors.black, size: 20), + ); } class ReloadButton extends ActionButton { const ReloadButton({super.key, super.onPressed}) - : super( - text: 'Reload', - icon: const Icon( - Icons.refresh, - color: Colors.black, - size: 20, - ), - ); + : super( + text: 'Reload', + icon: const Icon(Icons.refresh, color: Colors.black, size: 20), + ); } abstract class ActionButton extends StatelessWidget { @@ -132,8 +119,12 @@ abstract class ActionButton extends StatelessWidget { final String text; final Icon icon; - const ActionButton( - {this.onPressed, super.key, required this.text, required this.icon}); + const ActionButton({ + this.onPressed, + super.key, + required this.text, + required this.icon, + }); @override Widget build(BuildContext context) { @@ -142,27 +133,25 @@ abstract class ActionButton extends StatelessWidget { waitDuration: tooltipDelay, child: TextButton( style: ButtonStyle( - shape: const WidgetStatePropertyAll(RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4)))), - backgroundColor: WidgetStateProperty.resolveWith( - (states) { - if (states.contains(WidgetState.disabled)) { - return runButtonColor.withValues(alpha: 0.4); - } - - return runButtonColor; - }, + shape: const WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), ), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return runButtonColor.withValues(alpha: 0.4); + } + + return runButtonColor; + }), ), onPressed: onPressed, child: Row( children: [ icon, const SizedBox(width: 8), - Text( - text, - style: const TextStyle(color: Colors.black), - ), + Text(text, style: const TextStyle(color: Colors.black)), ], ), ), @@ -175,10 +164,7 @@ abstract class ActionButton extends StatelessWidget { class StatusWidget extends StatelessWidget { final StatusController status; - const StatusWidget({ - required this.status, - super.key, - }); + const StatusWidget({required this.status, super.key}); @override Widget build(BuildContext context) { @@ -195,9 +181,10 @@ class StatusWidget extends StatelessWidget { builder: (context, MessageStatus status, _) { return AnimatedOpacity( opacity: status.state == MessageState.closing ? 0.0 : 1.0, - duration: status.state == MessageState.showing - ? Duration.zero - : animationDelay, + duration: + status.state == MessageState.showing + ? Duration.zero + : animationDelay, curve: animationCurve, child: Material( shape: const StadiumBorder(), @@ -233,61 +220,59 @@ class MediumDialog extends StatelessWidget { @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - final width = smaller ? 400.0 : 500.0; - final height = smaller ? 325.0 : 400.0; - final theme = Theme.of(context); - - return PointerInterceptor( - child: AlertDialog( - backgroundColor: theme.scaffoldBackgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), - side: BorderSide( - color: theme.colorScheme.outline, - width: 1, + return LayoutBuilder( + builder: (context, constraints) { + final width = smaller ? 400.0 : 500.0; + final height = smaller ? 325.0 : 400.0; + final theme = Theme.of(context); + + return PointerInterceptor( + child: AlertDialog( + backgroundColor: theme.scaffoldBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + side: BorderSide(color: theme.colorScheme.outline, width: 1), ), - ), - title: Text(title, maxLines: 1), - contentTextStyle: theme.textTheme.bodyMedium, - contentPadding: const EdgeInsets.fromLTRB(24, defaultSpacing, 24, 8), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: width, - height: height, - child: ClipRect(child: child), + title: Text(title, maxLines: 1), + contentTextStyle: theme.textTheme.bodyMedium, + contentPadding: const EdgeInsets.fromLTRB( + 24, + defaultSpacing, + 24, + 8, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: width, + height: height, + child: ClipRect(child: child), + ), + const Divider(), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), ), - const Divider(), ], ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - }); + ); + }, + ); } } class GoldenRatioCenter extends StatelessWidget { final Widget child; - const GoldenRatioCenter({ - required this.child, - super.key, - }); + const GoldenRatioCenter({required this.child, super.key}); @override Widget build(BuildContext context) { - return Align( - alignment: const Alignment(0.0, -(1.618 / 4)), - child: child, - ); + return Align(alignment: const Alignment(0.0, -(1.618 / 4)), child: child); } } @@ -296,7 +281,7 @@ final class Logo extends StatelessWidget { final double width; const Logo({super.key, this.width = defaultIconSize, String? type}) - : _type = type; + : _type = type; @override Widget build(BuildContext context) { @@ -388,9 +373,10 @@ class _PromptDialogState extends State { spacing: 8, alignment: MainAxisAlignment.start, children: [ - for (final entry in _appType == AppType.flutter - ? widget.flutterPromptButtons.entries - : widget.dartPromptButtons.entries) + for (final entry + in _appType == AppType.flutter + ? widget.flutterPromptButtons.entries + : widget.dartPromptButtons.entries) TextButton( onPressed: () { _controller.text = entry.value; @@ -456,15 +442,17 @@ class _PromptDialogState extends State { ), ValueListenableBuilder( valueListenable: _controller, - builder: (context, controller, _) => TextButton( - onPressed: controller.text.isEmpty ? null : _onGenerate, - child: Text( - 'Generate', - style: TextStyle( - color: controller.text.isEmpty ? theme.disabledColor : null, + builder: + (context, controller, _) => TextButton( + onPressed: controller.text.isEmpty ? null : _onGenerate, + child: Text( + 'Generate', + style: TextStyle( + color: + controller.text.isEmpty ? theme.disabledColor : null, + ), + ), ), - ), - ), ), ], ), @@ -487,9 +475,7 @@ class _PromptDialogState extends State { setState(() => _attachments.removeAt(index)); Future _addAttachment() async { - final pic = await ImagePicker().pickImage( - source: ImageSource.gallery, - ); + final pic = await ImagePicker().pickImage(source: ImageSource.gallery); if (pic == null) return; @@ -533,13 +519,14 @@ class _GeneratingCodeDialogState extends State { _subscription = widget.stream.listen( (text) => setState(() => _generatedCode.write(text)), - onDone: () => setState(() { - final source = _generatedCode.toString().trim(); - _generatedCode.clear(); - _generatedCode.write(source); - _done = true; - _focusNode.requestFocus(); - }), + onDone: + () => setState(() { + final source = _generatedCode.toString().trim(); + _generatedCode.clear(); + _generatedCode.write(source); + _done = true; + _focusNode.requestFocus(); + }), ); } @@ -584,12 +571,13 @@ class _GeneratingCodeDialogState extends State { child: Focus( autofocus: true, focusNode: _focusNode, - child: widget.existingSource == null - ? ReadOnlyEditorWidget(_generatedCode.toString()) - : ReadOnlyDiffWidget( - existingSource: widget.existingSource!, - newSource: _generatedCode.toString(), - ), + child: + widget.existingSource == null + ? ReadOnlyEditorWidget(_generatedCode.toString()) + : ReadOnlyDiffWidget( + existingSource: widget.existingSource!, + newSource: _generatedCode.toString(), + ), ), ), actions: [ @@ -606,12 +594,13 @@ class _GeneratingCodeDialogState extends State { TextSpan( text: 'Google AI', style: TextStyle(color: theme.colorScheme.primary), - recognizer: TapGestureRecognizer() - ..onTap = () { - url_launcher.launchUrl( - Uri.parse('https://ai.google.dev/'), - ); - }, + recognizer: + TapGestureRecognizer() + ..onTap = () { + url_launcher.launchUrl( + Uri.parse('https://ai.google.dev/'), + ); + }, ), TextSpan( text: ' and the Gemini API', @@ -781,10 +770,7 @@ class _AddImageWidget extends StatelessWidget { shape: const RoundedRectangleBorder(), ), child: const Center( - child: Text( - 'Add\nImage', - textAlign: TextAlign.center, - ), + child: Text('Add\nImage', textAlign: TextAlign.center), ), ), ), diff --git a/pkgs/dartpad_ui/pubspec.yaml b/pkgs/dartpad_ui/pubspec.yaml index fc6dbdd36..ea0b2adbb 100644 --- a/pkgs/dartpad_ui/pubspec.yaml +++ b/pkgs/dartpad_ui/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.6.1 + sdk: ^3.7.0 dependencies: collection: ^1.19.0 diff --git a/pkgs/dartpad_ui/test/autosave_test.dart b/pkgs/dartpad_ui/test/autosave_test.dart index 28d23c450..54681ca96 100644 --- a/pkgs/dartpad_ui/test/autosave_test.dart +++ b/pkgs/dartpad_ui/test/autosave_test.dart @@ -34,9 +34,7 @@ void main() { LocalStorage.instance.saveUserCode(''); expect(LocalStorage.instance.getUserCode(), isNull); - await services.performInitialLoad( - getFallback: getFallback, - ); + await services.performInitialLoad(getFallback: getFallback); expect(model.sourceCodeController.text, equals(Samples.defaultSnippet())); }); @@ -49,9 +47,7 @@ void main() { final services = AppServices(model, channel); expect(LocalStorage.instance.getUserCode(), equals(sample)); - await services.performInitialLoad( - getFallback: getFallback, - ); + await services.performInitialLoad(getFallback: getFallback); expect(model.sourceCodeController.text, equals(sample)); }); diff --git a/pkgs/dartpad_ui/test/gists_test.dart b/pkgs/dartpad_ui/test/gists_test.dart index b63a9f4f1..aa30f8ed7 100644 --- a/pkgs/dartpad_ui/test/gists_test.dart +++ b/pkgs/dartpad_ui/test/gists_test.dart @@ -10,8 +10,9 @@ import 'package:test/test.dart'; void main() { group('gists', () { test('parses json', () { - final gist = - Gist.fromJson(jsonDecode(jsonSample) as Map); + final gist = Gist.fromJson( + jsonDecode(jsonSample) as Map, + ); expect(gist.id, 'd3bd83918d21b6d5f778bdc69c3d36d6'); expect(gist.description, 'Fibonacci'); @@ -20,29 +21,33 @@ void main() { }); test('finds main.dart', () { - final gist = - Gist.fromJson(jsonDecode(jsonSample) as Map); + final gist = Gist.fromJson( + jsonDecode(jsonSample) as Map, + ); expect(gist.mainDartSource, isNotNull); }); test('recognizes main.dart missing', () { - final gist = - Gist.fromJson(jsonDecode(jsonSampleNoMain) as Map); + final gist = Gist.fromJson( + jsonDecode(jsonSampleNoMain) as Map, + ); expect(gist.mainDartSource, isNull); }); test('validates main.dart missing', () { - final gist = - Gist.fromJson(jsonDecode(jsonSampleNoMain) as Map); + final gist = Gist.fromJson( + jsonDecode(jsonSampleNoMain) as Map, + ); expect(gist.validationIssues, isNotEmpty); }); test('validates unexpected dart content file', () { final gist = Gist.fromJson( - jsonDecode(jsonSampleAlternativeFile) as Map); + jsonDecode(jsonSampleAlternativeFile) as Map, + ); expect(gist.validationIssues, isNotEmpty); }); diff --git a/pkgs/dartpad_ui/test/model_test.dart b/pkgs/dartpad_ui/test/model_test.dart index 54b5d2d35..8dabeaadb 100644 --- a/pkgs/dartpad_ui/test/model_test.dart +++ b/pkgs/dartpad_ui/test/model_test.dart @@ -14,16 +14,7 @@ void main() { test('supported channels', () { final result = Channel.valuesWithoutLocalhost.map((c) => c.name).toList(); - expect( - result, - unorderedMatches( - [ - 'main', - 'beta', - 'stable', - ], - ), - ); + expect(result, unorderedMatches(['main', 'beta', 'stable'])); }); }); } diff --git a/pkgs/samples/lib/brick_breaker.dart b/pkgs/samples/lib/brick_breaker.dart index 313d93c3d..fcc8c3133 100644 --- a/pkgs/samples/lib/brick_breaker.dart +++ b/pkgs/samples/lib/brick_breaker.dart @@ -50,10 +50,7 @@ class _GameAppState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Color(0xffa9d6e5), - Color(0xfff2e8cf), - ], + colors: [Color(0xffa9d6e5), Color(0xfff2e8cf)], ), ), child: SafeArea( @@ -64,9 +61,7 @@ class _GameAppState extends State { child: SizedBox( width: gameWidth, height: gameHeight, - child: GameWidget( - game: game, - ), + child: GameWidget(game: game), ), ), ), @@ -81,9 +76,12 @@ class _GameAppState extends State { class BrickBreaker extends FlameGame with HasCollisionDetection, KeyboardEvents, TapDetector { BrickBreaker() - : super( - camera: CameraComponent.withFixedResolution( - width: gameWidth, height: gameHeight)); + : super( + camera: CameraComponent.withFixedResolution( + width: gameWidth, + height: gameHeight, + ), + ); final rand = math.Random(); double get width => size.x; @@ -102,20 +100,27 @@ class BrickBreaker extends FlameGame world.removeAll(world.children.query()); world.removeAll(world.children.query()); - world.add(Ball( - difficultyModifier: difficultyModifier, - radius: ballRadius, - position: size / 2, - velocity: - Vector2((rand.nextDouble() - 0.5) * width, height * 0.3).normalized() - ..scale(height / 4), - )); - - world.add(Paddle( - size: Vector2(paddleWidth, paddleHeight), - cornerRadius: const Radius.circular(ballRadius / 2), - position: Vector2(width / 2, height * 0.95), - )); + world.add( + Ball( + difficultyModifier: difficultyModifier, + radius: ballRadius, + position: size / 2, + velocity: + Vector2( + (rand.nextDouble() - 0.5) * width, + height * 0.3, + ).normalized() + ..scale(height / 4), + ), + ); + + world.add( + Paddle( + size: Vector2(paddleWidth, paddleHeight), + cornerRadius: const Radius.circular(ballRadius / 2), + position: Vector2(width / 2, height * 0.95), + ), + ); world.addAll([ for (var i = 0; i < brickColors.length; i++) @@ -162,12 +167,14 @@ class Ball extends CircleComponent required double radius, required this.difficultyModifier, }) : super( - radius: radius, - anchor: Anchor.center, - paint: Paint() - ..color = const Color(0xff1e6091) - ..style = PaintingStyle.fill, - children: [CircleHitbox()]); + radius: radius, + anchor: Anchor.center, + paint: + Paint() + ..color = const Color(0xff1e6091) + ..style = PaintingStyle.fill, + children: [CircleHitbox()], + ); final Vector2 velocity; final double difficultyModifier; @@ -180,7 +187,9 @@ class Ball extends CircleComponent @override void onCollisionStart( - Set intersectionPoints, PositionComponent other) { + Set intersectionPoints, + PositionComponent other, + ) { super.onCollisionStart(intersectionPoints, other); if (other is PlayArea) { if (intersectionPoints.first.y <= 0) { @@ -190,16 +199,19 @@ class Ball extends CircleComponent } else if (intersectionPoints.first.x >= game.width) { velocity.x = -velocity.x; } else if (intersectionPoints.first.y >= game.height) { - add(RemoveEffect( - delay: 0.35, - onComplete: () { - game.startGame(); - }, - )); + add( + RemoveEffect( + delay: 0.35, + onComplete: () { + game.startGame(); + }, + ), + ); } } else if (other is Paddle) { velocity.y = -velocity.y; - velocity.x = velocity.x + + velocity.x = + velocity.x + (position.x - other.position.x) / other.size.x * game.width * 0.3; } else if (other is Brick) { if (position.y < other.position.y - other.size.y / 2) { @@ -226,9 +238,10 @@ class Paddle extends PositionComponent final Radius cornerRadius; - final _paint = Paint() - ..color = const Color(0xff1e6091) - ..style = PaintingStyle.fill; + final _paint = + Paint() + ..color = const Color(0xff1e6091) + ..style = PaintingStyle.fill; @override void update(double dt) { @@ -237,12 +250,16 @@ class Paddle extends PositionComponent final keysPressed = HardwareKeyboard.instance.logicalKeysPressed; if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) || keysPressed.contains(LogicalKeyboardKey.keyA)) { - position.x = - (position.x - (dt * 500)).clamp(width / 2, game.width - width / 2); + position.x = (position.x - (dt * 500)).clamp( + width / 2, + game.width - width / 2, + ); } else if (keysPressed.contains(LogicalKeyboardKey.arrowRight) || keysPressed.contains(LogicalKeyboardKey.keyD)) { - position.x = - (position.x + (dt * 500)).clamp(width / 2, game.width - width / 2); + position.x = (position.x + (dt * 500)).clamp( + width / 2, + game.width - width / 2, + ); } } @@ -250,10 +267,7 @@ class Paddle extends PositionComponent void render(Canvas canvas) { super.render(canvas); canvas.drawRRect( - RRect.fromRectAndRadius( - Offset.zero & size.toSize(), - cornerRadius, - ), + RRect.fromRectAndRadius(Offset.zero & size.toSize(), cornerRadius), _paint, ); } @@ -262,27 +276,32 @@ class Paddle extends PositionComponent void onDragUpdate(DragUpdateEvent event) { if (isRemoved) return; super.onDragUpdate(event); - position.x = (position.x + event.localDelta.x) - .clamp(width / 2, game.width - width / 2); + position.x = (position.x + event.localDelta.x).clamp( + width / 2, + game.width - width / 2, + ); } } class Brick extends RectangleComponent with CollisionCallbacks, HasGameReference { Brick(Vector2 position, Color color) - : super( - position: position, - size: Vector2(brickWidth, brickHeight), - anchor: Anchor.center, - paint: Paint() - ..color = color - ..style = PaintingStyle.fill, - children: [RectangleHitbox()], - ); + : super( + position: position, + size: Vector2(brickWidth, brickHeight), + anchor: Anchor.center, + paint: + Paint() + ..color = color + ..style = PaintingStyle.fill, + children: [RectangleHitbox()], + ); @override void onCollisionStart( - Set intersectionPoints, PositionComponent other) { + Set intersectionPoints, + PositionComponent other, + ) { super.onCollisionStart(intersectionPoints, other); removeFromParent(); diff --git a/pkgs/samples/lib/default_flutter.dart b/pkgs/samples/lib/default_flutter.dart index 277190914..ed9d0cc13 100644 --- a/pkgs/samples/lib/default_flutter.dart +++ b/pkgs/samples/lib/default_flutter.dart @@ -15,11 +15,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, - home: Scaffold( - body: Center( - child: Text('Hello, World!'), - ), - ), + home: Scaffold(body: Center(child: Text('Hello, World!'))), ); } } diff --git a/pkgs/samples/lib/google_ai.dart b/pkgs/samples/lib/google_ai.dart index 966ce9597..b0766dd60 100644 --- a/pkgs/samples/lib/google_ai.dart +++ b/pkgs/samples/lib/google_ai.dart @@ -46,14 +46,14 @@ class _ChatScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: switch (apiKey) { final providedKey? => ChatWidget(apiKey: providedKey), - _ => ApiKeyWidget(onSubmitted: (key) { + _ => ApiKeyWidget( + onSubmitted: (key) { setState(() => apiKey = key); - }), + }, + ), }, ); } @@ -82,18 +82,21 @@ class ApiKeyWidget extends StatelessWidget { Link( uri: Uri.https('makersuite.google.com', '/app/apikey'), target: LinkTarget.blank, - builder: (context, followLink) => TextButton( - onPressed: followLink, - child: const Text('Get an API Key'), - ), + builder: + (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('Get an API Key'), + ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( - decoration: - textFieldDecoration(context, 'Enter your API key'), + decoration: textFieldDecoration( + context, + 'Enter your API key', + ), controller: _textController, onSubmitted: (value) { onSubmitted(value); @@ -136,10 +139,7 @@ class _ChatWidgetState extends State { @override void initState() { super.initState(); - _model = GenerativeModel( - model: 'gemini-pro', - apiKey: widget.apiKey, - ); + _model = GenerativeModel(model: 'gemini-pro', apiKey: widget.apiKey); _chat = _model.startChat(); } @@ -147,9 +147,7 @@ class _ChatWidgetState extends State { WidgetsBinding.instance.addPostFrameCallback( (_) => _scrollController.animateTo( _scrollController.position.maxScrollExtent, - duration: const Duration( - milliseconds: 750, - ), + duration: const Duration(milliseconds: 750), curve: Curves.easeOutCirc, ), ); @@ -182,18 +180,17 @@ class _ChatWidgetState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, - horizontal: 15, - ), + padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15), child: Row( children: [ Expanded( child: TextField( autofocus: true, focusNode: _textFieldFocus, - decoration: - textFieldDecoration(context, 'Enter a prompt...'), + decoration: textFieldDecoration( + context, + 'Enter a prompt...', + ), controller: _textController, onSubmitted: (String value) { _sendChatMessage(value); @@ -227,9 +224,7 @@ class _ChatWidgetState extends State { }); try { - final response = await _chat.sendMessage( - Content.text(message), - ); + final response = await _chat.sendMessage(Content.text(message)); final text = response.text; if (text == null) { @@ -261,16 +256,14 @@ class _ChatWidgetState extends State { builder: (context) { return AlertDialog( title: const Text('Something went wrong'), - content: SingleChildScrollView( - child: Text(message), - ), + content: SingleChildScrollView(child: Text(message)), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('OK'), - ) + ), ], ); }, @@ -298,15 +291,13 @@ class MessageWidget extends StatelessWidget { child: Container( constraints: const BoxConstraints(maxWidth: 480), decoration: BoxDecoration( - color: isFromUser - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surfaceContainerHighest, + color: + isFromUser + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(18), ), - padding: const EdgeInsets.symmetric( - vertical: 15, - horizontal: 20, - ), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), margin: const EdgeInsets.only(bottom: 8), child: MarkdownBody(data: text), ), @@ -321,19 +312,11 @@ InputDecoration textFieldDecoration(BuildContext context, String hintText) => contentPadding: const EdgeInsets.all(15), hintText: hintText, border: OutlineInputBorder( - borderRadius: const BorderRadius.all( - Radius.circular(14), - ), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.secondary, - ), + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), ), focusedBorder: OutlineInputBorder( - borderRadius: const BorderRadius.all( - Radius.circular(14), - ), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.secondary, - ), + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), ), ); diff --git a/pkgs/samples/lib/main.dart b/pkgs/samples/lib/main.dart index 9c417491e..00db983f3 100644 --- a/pkgs/samples/lib/main.dart +++ b/pkgs/samples/lib/main.dart @@ -14,9 +14,7 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, - theme: ThemeData( - colorSchemeSeed: Colors.blue, - ), + theme: ThemeData(colorSchemeSeed: Colors.blue), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } @@ -25,10 +23,7 @@ class MyApp extends StatelessWidget { class MyHomePage extends StatefulWidget { final String title; - const MyHomePage({ - super.key, - required this.title, - }); + const MyHomePage({super.key, required this.title}); @override State createState() => _MyHomePageState(); @@ -46,16 +41,12 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - 'You have pushed the button this many times:', - ), + const Text('You have pushed the button this many times:'), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, diff --git a/pkgs/samples/lib/sunflower.dart b/pkgs/samples/lib/sunflower.dart index c7beffb3f..21c8e200d 100644 --- a/pkgs/samples/lib/sunflower.dart +++ b/pkgs/samples/lib/sunflower.dart @@ -33,16 +33,12 @@ class _SunflowerState extends State { ), debugShowCheckedModeBanner: false, home: Scaffold( - appBar: AppBar( - title: const Text('Sunflower'), - ), + appBar: AppBar(title: const Text('Sunflower')), body: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded( - child: SunflowerWidget(seeds), - ), + Expanded(child: SunflowerWidget(seeds)), const SizedBox(height: 20), Text('Showing ${seeds.round()} seeds'), SizedBox( @@ -84,26 +80,30 @@ class SunflowerWidget extends StatelessWidget { final theta = i * tau / phi; final r = math.sqrt(i) * scaleFactor; - seedWidgets.add(AnimatedAlign( - key: ValueKey(i), - duration: Duration(milliseconds: rng.nextInt(500) + 250), - curve: Curves.easeInOut, - alignment: Alignment(r * math.cos(theta), -1 * r * math.sin(theta)), - child: const Dot(true), - )); + seedWidgets.add( + AnimatedAlign( + key: ValueKey(i), + duration: Duration(milliseconds: rng.nextInt(500) + 250), + curve: Curves.easeInOut, + alignment: Alignment(r * math.cos(theta), -1 * r * math.sin(theta)), + child: const Dot(true), + ), + ); } for (var j = seeds; j < maxSeeds; j++) { final x = math.cos(tau * j / (maxSeeds - 1)) * 0.9; final y = math.sin(tau * j / (maxSeeds - 1)) * 0.9; - seedWidgets.add(AnimatedAlign( - key: ValueKey(j), - duration: Duration(milliseconds: rng.nextInt(500) + 250), - curve: Curves.easeInOut, - alignment: Alignment(x, y), - child: const Dot(false), - )); + seedWidgets.add( + AnimatedAlign( + key: ValueKey(j), + duration: Duration(milliseconds: rng.nextInt(500) + 250), + curve: Curves.easeInOut, + alignment: Alignment(x, y), + child: const Dot(false), + ), + ); } return FittedBox( @@ -132,10 +132,7 @@ class Dot extends StatelessWidget { color: lit ? Colors.orange : Colors.grey.shade700, borderRadius: BorderRadius.circular(radius), ), - child: const SizedBox( - height: size, - width: size, - ), + child: const SizedBox(height: size, width: size), ); } } diff --git a/pkgs/samples/pubspec.yaml b/pkgs/samples/pubspec.yaml index 98460b04c..485c9e228 100644 --- a/pkgs/samples/pubspec.yaml +++ b/pkgs/samples/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none resolution: workspace environment: - sdk: ^3.6.1 + sdk: ^3.7.0 dependencies: flame: ^1.25.0 diff --git a/pkgs/samples/tool/samples.dart b/pkgs/samples/tool/samples.dart index 5e2940100..4d91af7a5 100644 --- a/pkgs/samples/tool/samples.dart +++ b/pkgs/samples/tool/samples.dart @@ -9,11 +9,19 @@ import 'package:args/args.dart'; import 'package:path/path.dart' as p; void main(List args) { - final argParser = ArgParser() - ..addFlag('verify', - negatable: false, help: 'Verify the generated samples files.') - ..addFlag('help', - abbr: 'h', negatable: false, help: 'Display this help output.'); + final argParser = + ArgParser() + ..addFlag( + 'verify', + negatable: false, + help: 'Verify the generated samples files.', + ) + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Display this help output.', + ); final argResults = argParser.parse(args); @@ -31,20 +39,16 @@ void main(List args) { } } -const Set categories = { - 'Defaults', - 'Dart', - 'Flutter', - 'Ecosystem', -}; +const Set categories = {'Defaults', 'Dart', 'Flutter', 'Ecosystem'}; class Samples { late final List samples; void parse() { // read the samples - final json = - jsonDecode(File(p.join('lib', 'samples.json')).readAsStringSync()); + final json = jsonDecode( + File(p.join('lib', 'samples.json')).readAsStringSync(), + ); samples = (json as List).map((j) => Sample.fromJson(j)).toList(); @@ -200,10 +204,19 @@ abstract final class Samples { } String _mapForCategory(String category) { - final items = samples.where((s) => s.category == category); - return ''''$category': [ - ${items.map((i) => i.sourceId).join(',\n ')}, - ]'''; + final items = samples.where((s) => s.category == category).toList(); + final buffer = StringBuffer(); + buffer.write("'$category': ["); + //Put a comma between each item, but not after the last item + for (var i = 0; i < items.length; i++) { + buffer.write(items[i].sourceId); + if (i < items.length - 1) { + buffer.write(', '); + } + } + buffer.write(']'); + + return buffer.toString(); } } @@ -236,7 +249,8 @@ class Sample implements Comparable { var gen = id; while (gen.contains('-')) { final index = id.indexOf('-'); - gen = gen.substring(0, index) + + gen = + gen.substring(0, index) + gen.substring(index + 1, index + 2).toUpperCase() + gen.substring(index + 2); } @@ -280,6 +294,9 @@ $source static String _idFromName(String name) => name.trim().toLowerCase().replaceAll(' ', '-'); - static final RegExp _copyrightCommentPattern = - RegExp(r'^\/\/ Copyright.*LICENSE file.', multiLine: true, dotAll: true); + static final RegExp _copyrightCommentPattern = RegExp( + r'^\/\/ Copyright.*LICENSE file.', + multiLine: true, + dotAll: true, + ); }