From 99e80fabaa7afea44b78afa55146148e59d35a6f Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 16 Oct 2023 13:36:54 -0700 Subject: [PATCH] Add additional functionality to the v3 protocol (#2676) * implement v3 versions of document, fix * add quick fixes, assists to v3 protocol * remove extra changes --- .github/dependabot.yaml | 16 -- pkgs/dart_pad/lib/parameter_popup.dart | 233 ------------------ pkgs/dart_pad/lib/services/dartservices.dart | 24 +- pkgs/dart_pad/pubspec.yaml | 2 +- .../lib/src/analysis_server.dart | 75 ++++++ .../lib/src/analyzer_wrapper.dart | 7 + .../lib/src/common_server_api.dart | 77 ++++-- pkgs/dart_services/test/server_v3_test.dart | 89 +++++++ pkgs/dartpad_shared/analysis_options.yaml | 1 + pkgs/dartpad_shared/lib/model.dart | 82 ++++++ pkgs/dartpad_shared/lib/model.g.dart | 62 +++++ pkgs/dartpad_shared/lib/services.dart | 39 ++- pkgs/dartpad_shared/pubspec.yaml | 3 +- pkgs/sketch_pad/lib/main.dart | 6 +- pkgs/sketch_pad/pubspec.yaml | 2 +- 15 files changed, 418 insertions(+), 300 deletions(-) delete mode 100644 pkgs/dart_pad/lib/parameter_popup.dart create mode 100644 pkgs/dartpad_shared/analysis_options.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index f3712683c..df4368d31 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -9,19 +9,3 @@ updates: labels: - "autosubmit" - "type-infra" - - - package-ecosystem: "pub" - directory: "/pkgs/dart_pad/" - schedule: - interval: "monthly" - versioning-strategy: increase-if-necessary - labels: - - "type-infra" - - - package-ecosystem: "pub" - directory: "/pkgs/dart_services/" - schedule: - interval: "monthly" - versioning-strategy: increase-if-necessary - labels: - - "type-infra" diff --git a/pkgs/dart_pad/lib/parameter_popup.dart b/pkgs/dart_pad/lib/parameter_popup.dart deleted file mode 100644 index daf9d47f8..000000000 --- a/pkgs/dart_pad/lib/parameter_popup.dart +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:convert'; -import 'dart:html'; -import 'dart:math' as math; - -import 'context.dart'; -import 'dart_pad.dart'; -import 'editing/editor.dart'; -import 'services/common.dart'; -import 'services/dartservices.dart'; - -class ParameterPopup { - static const Set parKeys = { - KeyCode.COMMA, - KeyCode.NINE, - KeyCode.ZERO, - KeyCode.LEFT, - KeyCode.RIGHT, - KeyCode.UP, - KeyCode.DOWN - }; - - final Context context; - final Editor editor; - - final HtmlEscape sanitizer = const HtmlEscape(); - - ParameterPopup(this.context, this.editor) { - document.onKeyDown.listen(_handleKeyDown); - document.onKeyUp.listen(_handleKeyUp); - document.onClick.listen((e) => _handleClick()); - editor.onMouseDown.listen((e) => _handleClick()); - } - - bool get parPopupActive => querySelector('.parameter-hints') != null; - - void remove() { - document.body!.children.remove(querySelector('.parameter-hints')); - } - - void _handleKeyDown(KeyboardEvent e) { - if (e.keyCode == KeyCode.ENTER) { - // TODO: Use the onClose event of the completion event to trigger this - _lookupParameterInfo(); - } - } - - void _handleKeyUp(KeyboardEvent e) { - if (e.keyCode == KeyCode.ESC || - context.focusedEditor != 'dart' || - !editor.hasFocus) { - remove(); - } else if (parPopupActive || parKeys.contains(e.keyCode)) { - _lookupParameterInfo(); - } - } - - void _handleClick() { - if (context.focusedEditor != 'dart' || !editor.hasFocus) { - remove(); - } else { - _lookupParameterInfo(); - } - } - - void _lookupParameterInfo() { - var offset = editor.document.indexFromPos(editor.document.cursor); - final source = editor.document.value; - var parInfo = _parameterInfo(source, offset); - - if (parInfo == null) { - remove(); - return; - } - - final openingParenIndex = parInfo['openingParenIndex']!; - final parameterIndex = parInfo['parameterIndex']; - offset = openingParenIndex - 1; - - // We request documentation info of what is before the parenthesis. - final input = SourceRequest() - ..source = source - ..offset = offset; - - dartServices - .document(input) - .timeout(documentServiceTimeout) - .then((DocumentResponse result) { - if (!result.info.containsKey('parameters')) { - remove(); - return; - } - - final parameterInfo = result.info['parameters']!; - var outputString = ''; - if (parameterInfo.isEmpty) { - outputString += '<no parameters>'; - } else if (parameterInfo.length < parameterIndex! + 1) { - outputString += 'too many parameters listed'; - } else { - outputString += ''; - - for (var i = 0; i < parameterInfo.length; i++) { - if (i == parameterIndex) { - outputString += '${sanitizer.convert(parameterInfo[i])}'; - } else { - outputString += - '${sanitizer.convert(parameterInfo[i])}'; - } - if (i != parameterInfo.length - 1) { - outputString += ', '; - } - } - outputString += ''; - } - - // Check if cursor is still in parameter position - parInfo = _parameterInfo(editor.document.value, - editor.document.indexFromPos(editor.document.cursor)); - if (parInfo == null) { - remove(); - return; - } - _showParameterPopup(outputString, offset); - }); - } - - void _showParameterPopup(String string, int methodOffset) { - final editorDiv = querySelector('#editpanel .CodeMirror') as DivElement; - final lineHeightStr = - editorDiv.getComputedStyle().getPropertyValue('line-height'); - final lineHeight = - int.parse(lineHeightStr.substring(0, lineHeightStr.indexOf('px'))); - // var charWidth = editorDiv.getComputedStyle().getPropertyValue('letter-spacing'); - final charWidth = 8; - - final methodPosition = editor.document.posFromIndex(methodOffset); - final cursorCoords = editor.getCursorCoords(); - final methodCoords = editor.getCursorCoords(position: methodPosition); - final heightOfMethod = (methodCoords.y - lineHeight - 5).round(); - - DivElement parameterPopup; - if (parPopupActive) { - final parameterHint = querySelector('.parameter-hint')!; - parameterHint.innerHtml = string; - - //update popup position - final newLeft = math - .max( - cursorCoords.x - (parameterHint.text!.length * charWidth / 2), 22) - .round(); - - parameterPopup = querySelector('.parameter-hints') as DivElement - ..style.top = '${heightOfMethod}px'; - final oldLeftString = parameterPopup.style.left; - final oldLeft = - int.parse(oldLeftString.substring(0, oldLeftString.indexOf('px'))); - if ((newLeft - oldLeft).abs() > 50) { - parameterPopup.style.left = '${newLeft}px'; - } - } else { - final parameterHint = SpanElement() - ..innerHtml = string - ..classes.add('parameter-hint'); - final left = math - .max( - cursorCoords.x - (parameterHint.text!.length * charWidth / 2), 22) - .round(); - parameterPopup = DivElement() - ..classes.add('parameter-hints') - ..style.left = '${left}px' - ..style.top = '${heightOfMethod}px' - ..style.maxWidth = - "${querySelector("#editpanel")!.getBoundingClientRect().width}px"; - parameterPopup.append(parameterHint); - document.body!.append(parameterPopup); - } - final activeParameter = querySelector('.parameter-hints em'); - if (activeParameter != null && - activeParameter.previousElementSibling != null) { - parameterPopup.scrollLeft = - activeParameter.previousElementSibling!.offsetLeft; - } - } - - /// Returns null if the offset is not contained in parenthesis. - /// Otherwise it will return information about the parameters. - /// For example, if the source is `substring(1, )`, it will return - /// `{openingParenIndex: 9, parameterIndex: 1}`. - Map? _parameterInfo(String source, int offset) { - var parameterIndex = 0; - int? openingParenIndex; - var nesting = 0; - - while (openingParenIndex == null && offset > 0) { - offset += -1; - if (nesting == 0) { - switch (source[offset]) { - case '(': - openingParenIndex = offset; - break; - case ',': - parameterIndex += 1; - break; - case ';': - return null; - case ')': - nesting += 1; - break; - } - } else { - switch (source[offset]) { - case '(': - nesting += -1; - break; - case ')': - nesting += 1; - break; - } - } - } - - return openingParenIndex == null - ? null - : { - 'openingParenIndex': openingParenIndex, - 'parameterIndex': parameterIndex - }; - } -} diff --git a/pkgs/dart_pad/lib/services/dartservices.dart b/pkgs/dart_pad/lib/services/dartservices.dart index ca34a6859..45ff65ae5 100644 --- a/pkgs/dart_pad/lib/services/dartservices.dart +++ b/pkgs/dart_pad/lib/services/dartservices.dart @@ -63,9 +63,8 @@ class DartservicesApi { FormatResponse(), ); - Future version() => _request( + Future version() => _requestGet( 'version', - VersionRequest(), VersionResponse(), ); @@ -94,6 +93,27 @@ class DartservicesApi { return result; } + + Future _requestGet( + String action, + O result, + ) async { + final response = await _client.get(Uri.parse('$rootUrl$_apiPath/$action')); + final jsonBody = json.decode(response.body); + result + ..mergeFromProto3Json(jsonBody, ignoreUnknownFields: true) + ..freeze(); + + // 99 is the tag number for error message. + if (result.getFieldOrNull(99) != null) { + final br = BadRequest() + ..mergeFromProto3Json(jsonBody) + ..freeze(); + throw ApiRequestError(br.error.message); + } + + return result; + } } class ApiRequestError implements Exception { diff --git a/pkgs/dart_pad/pubspec.yaml b/pkgs/dart_pad/pubspec.yaml index 560c78c58..c009418fe 100644 --- a/pkgs/dart_pad/pubspec.yaml +++ b/pkgs/dart_pad/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: html_unescape: ^2.0.0 http: ^0.13.0 js: ^0.6.7 - json_annotation: ^4.8.0 + json_annotation: ^4.8.1 logging: ^1.2.0 markdown: ^7.0.1 mdc_web: ^0.6.0 diff --git a/pkgs/dart_services/lib/src/analysis_server.dart b/pkgs/dart_services/lib/src/analysis_server.dart index 8f26c291f..5bd9e1918 100644 --- a/pkgs/dart_services/lib/src/analysis_server.dart +++ b/pkgs/dart_services/lib/src/analysis_server.dart @@ -207,6 +207,38 @@ abstract class AnalysisServerWrapper { return proto.FixesResponse()..fixes.addAll(responseFixes); } + Future fixesV3(String src, int offset) async { + final mainFile = _getPathFromName(kMainDart); + final overlay = {mainFile: src}; + + await _loadSources(overlay); + + try { + final fixes = await analysisServer.edit.getFixes(mainFile, offset); + final assists = await analysisServer.edit.getAssists(mainFile, offset, 1); + + final fixChanges = fixes.fixes.expand((fixes) => fixes.fixes).toList(); + final assistsChanges = assists.assists; + + // Filter any source changes that want to act on files other than main.dart. + fixChanges.removeWhere( + (change) => change.edits.any((edit) => edit.file != mainFile)); + assistsChanges.removeWhere( + (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(), + ); + } finally { + await _unloadSources(); + } + } + Future getAssists(String src, int offset) async { return getAssistsMulti({kMainDart: src}, Location(kMainDart, offset)); } @@ -285,6 +317,33 @@ abstract class AnalysisServerWrapper { }; } + Future dartdocV3(String src, int offset) async { + final location = Location(kMainDart, offset); + final sources = _getOverlayMapWithPaths({kMainDart: src}); + final sourcepath = _getPathFromName(location.sourceName); + + await _loadSources(sources); + + final result = + await analysisServer.analysis.getHover(sourcepath, location.offset); + await _unloadSources(); + + if (result.hovers.isEmpty) { + return api.DocumentResponse(); + } + + final info = result.hovers.first; + + return api.DocumentResponse( + dartdoc: info.dartdoc, + containingLibraryName: info.containingLibraryName, + elementDescription: info.elementDescription, + elementKind: info.elementKind, + deprecated: info.isDeprecated, + propagatedType: info.propagatedType, + ); + } + Future analyze(String src) { return analyzeFiles({kMainDart: src}); } @@ -619,3 +678,19 @@ class Location { const Location(this.sourceName, this.offset); } + +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(), + ); + } +} diff --git a/pkgs/dart_services/lib/src/analyzer_wrapper.dart b/pkgs/dart_services/lib/src/analyzer_wrapper.dart index 1a8bc91cb..9ff7dc324 100644 --- a/pkgs/dart_services/lib/src/analyzer_wrapper.dart +++ b/pkgs/dart_services/lib/src/analyzer_wrapper.dart @@ -120,6 +120,9 @@ class AnalyzerWrapper { 'Error during fixes on "${sources[activeSourceName]}" at $offset', ); + Future fixesV3(String source, int offset) => + _dartAnalysisServer.fixesV3(source, offset); + Future getAssists(String source, int offset) => getAssistsMulti({kMainDart: source}, kMainDart, offset); @@ -162,6 +165,10 @@ class AnalyzerWrapper { 'Error during dartdoc on "${sources[activeSourceName]}" at $offset', ); + Future dartdocV3(String source, int offset) { + return _dartAnalysisServer.dartdocV3(source, offset); + } + Future _perfLogAndRestart( Map sources, String activeSourceName, diff --git a/pkgs/dart_services/lib/src/common_server_api.dart b/pkgs/dart_services/lib/src/common_server_api.dart index bf1895af2..1e60ee7df 100644 --- a/pkgs/dart_services/lib/src/common_server_api.dart +++ b/pkgs/dart_services/lib/src/common_server_api.dart @@ -163,25 +163,39 @@ class CommonServerApi { } @Route.post('$apiPrefix/fixes') - Future fixes(Request request, String apiVersion) { - return _processRequest( - request, - decodeFromJSON: (json) => - proto.SourceRequest.create()..mergeFromProto3Json(json), - decodeFromProto: proto.SourceRequest.fromBuffer, - transform: _impl.fixes, - ); + Future fixes(Request request, String apiVersion) async { + if (apiVersion == api2) { + return _processRequest( + request, + decodeFromJSON: (json) => + proto.SourceRequest.create()..mergeFromProto3Json(json), + decodeFromProto: proto.SourceRequest.fromBuffer, + transform: _impl.fixes, + ); + } else if (apiVersion == api3) { + final sourceRequest = + api.SourceRequest.fromJson(await request.readAsJson()); + final result = await serialize(() => _impl.analysisServer + .fixesV3(sourceRequest.source, sourceRequest.offset!)); + return ok(result.toJson()); + } else { + return unhandledVersion(apiVersion); + } } @Route.post('$apiPrefix/assists') - Future assists(Request request, String apiVersion) { - return _processRequest( - request, - decodeFromJSON: (json) => - proto.SourceRequest.create()..mergeFromProto3Json(json), - decodeFromProto: proto.SourceRequest.fromBuffer, - transform: _impl.assists, - ); + Future assists(Request request, String apiVersion) async { + if (apiVersion == api2) { + return _processRequest( + request, + decodeFromJSON: (json) => + proto.SourceRequest.create()..mergeFromProto3Json(json), + decodeFromProto: proto.SourceRequest.fromBuffer, + transform: _impl.assists, + ); + } else { + return unhandledVersion(apiVersion); + } } @Route.post('$apiPrefix/format') @@ -214,14 +228,29 @@ class CommonServerApi { } @Route.post('$apiPrefix/document') - Future document(Request request, String apiVersion) { - return _processRequest( - request, - decodeFromJSON: (json) => - proto.SourceRequest.create()..mergeFromProto3Json(json), - decodeFromProto: proto.SourceRequest.fromBuffer, - transform: _impl.document, - ); + Future document(Request request, String apiVersion) async { + if (apiVersion == api2) { + return _processRequest( + request, + decodeFromJSON: (json) => + proto.SourceRequest.create()..mergeFromProto3Json(json), + decodeFromProto: proto.SourceRequest.fromBuffer, + transform: _impl.document, + ); + } else if (apiVersion == api3) { + final sourceRequest = + api.SourceRequest.fromJson(await request.readAsJson()); + + final result = await serialize(() { + return _impl.analysisServer.dartdocV3( + sourceRequest.source, + sourceRequest.offset!, + ); + }); + return ok(result.toJson()); + } else { + return unhandledVersion(apiVersion); + } } @Route.post('$apiPrefix/version') diff --git a/pkgs/dart_services/test/server_v3_test.dart b/pkgs/dart_services/test/server_v3_test.dart index c99085447..581fc4f05 100644 --- a/pkgs/dart_services/test/server_v3_test.dart +++ b/pkgs/dart_services/test/server_v3_test.dart @@ -183,5 +183,94 @@ void main() { expect(e.body, contains("Expected ';' after this.")); } }); + + test('document', () async { + final result = await client.document(SourceRequest( + source: ''' +void main() { + print('hello world'); +} +''', + offset: 18, + )); + + expect( + result.dartdoc!.toLowerCase(), + contains('prints a string representation'), + ); + expect(result.containingLibraryName, 'dart:core'); + expect(result.elementDescription, isNotNull); + expect(result.deprecated, false); + expect(result.propagatedType, isNull); + expect(result.elementKind, 'function'); + }); + + test('document empty', () async { + final result = await client.document(SourceRequest( + source: ''' +void main() { + print('hello world'); +} +''', + offset: 15, + )); + + expect(result.dartdoc, isNull); + expect(result.elementKind, isNull); + expect(result.elementDescription, isNull); + }); + + test('fixes', () async { + final result = await client.fixes(SourceRequest( + source: ''' +void main() { + var foo = 'bar'; + print('hello world'); +} +''', + offset: 21, + )); + + expect(result.fixes, hasLength(3)); + + final fix = result.fixes + .firstWhereOrNull((fix) => fix.message.contains('Ignore')); + expect(fix, isNotNull); + expect(fix!.edits, hasLength(1)); + expect(fix.edits.first.replacement, + contains('// ignore: unused_local_variable')); + }); + + test('fixes empty', () async { + final result = await client.fixes(SourceRequest( + source: ''' +void main() { + var foo = 'bar'; + print(foo); +} +''', + offset: 21, + )); + + expect(result.fixes, hasLength(0)); + }); + + test('assists', () async { + final result = await client.fixes(SourceRequest( + source: ''' +void main() => print('hello world'); +''', + offset: 13, + )); + + expect(result.fixes, hasLength(0)); + expect(result.assists, isNotEmpty); + + final assist = result.assists.firstWhereOrNull( + (assist) => assist.message.contains('Convert to block body')); + expect(assist, isNotNull); + expect(assist!.edits, hasLength(1)); + expect(assist.edits.first.replacement, isNotEmpty); + }); }); } diff --git a/pkgs/dartpad_shared/analysis_options.yaml b/pkgs/dartpad_shared/analysis_options.yaml new file mode 100644 index 000000000..d978f811c --- /dev/null +++ b/pkgs/dartpad_shared/analysis_options.yaml @@ -0,0 +1 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml diff --git a/pkgs/dartpad_shared/lib/model.dart b/pkgs/dartpad_shared/lib/model.dart index aa624606e..fa9544165 100644 --- a/pkgs/dartpad_shared/lib/model.dart +++ b/pkgs/dartpad_shared/lib/model.dart @@ -118,6 +118,88 @@ class FlutterBuildResponse { Map toJson() => _$FlutterBuildResponseToJson(this); } +@JsonSerializable() +class FixesResponse { + final List fixes; + final List assists; + + FixesResponse({ + required this.fixes, + required this.assists, + }); + + factory FixesResponse.fromJson(Map json) => + _$FixesResponseFromJson(json); + + Map toJson() => _$FixesResponseToJson(this); +} + +@JsonSerializable() +class SourceChange { + final String message; + final List edits; + // TODO: Add linked edit groups once we start using them. + // final List linkedEditGroups; + + SourceChange({ + required this.message, + required this.edits, + }); + + factory SourceChange.fromJson(Map json) => + _$SourceChangeFromJson(json); + + Map toJson() => _$SourceChangeToJson(this); + + @override + String toString() => 'SourceChange [$message]'; +} + +@JsonSerializable() +class SourceEdit { + final int offset; + final int length; + final String replacement; + + SourceEdit({ + required this.offset, + required this.length, + required this.replacement, + }); + + factory SourceEdit.fromJson(Map json) => + _$SourceEditFromJson(json); + + Map toJson() => _$SourceEditToJson(this); + + @override + String toString() => 'SourceEdit [$offset,$length,$replacement]'; +} + +@JsonSerializable() +class DocumentResponse { + final String? dartdoc; + final String? elementKind; + final String? elementDescription; + final String? containingLibraryName; + final bool? deprecated; + final String? propagatedType; + + DocumentResponse({ + this.dartdoc, + this.elementKind, + this.elementDescription, + this.containingLibraryName, + this.deprecated, + this.propagatedType, + }); + + factory DocumentResponse.fromJson(Map json) => + _$DocumentResponseFromJson(json); + + Map toJson() => _$DocumentResponseToJson(this); +} + @JsonSerializable() class CompleteResponse { final int replacementOffset; diff --git a/pkgs/dartpad_shared/lib/model.g.dart b/pkgs/dartpad_shared/lib/model.g.dart index 801f6ca5d..b65de8fb0 100644 --- a/pkgs/dartpad_shared/lib/model.g.dart +++ b/pkgs/dartpad_shared/lib/model.g.dart @@ -97,6 +97,68 @@ Map _$FlutterBuildResponseToJson( 'artifacts': instance.artifacts, }; +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(), + ); + +Map _$FixesResponseToJson(FixesResponse instance) => + { + 'fixes': instance.fixes, + 'assists': instance.assists, + }; + +SourceChange _$SourceChangeFromJson(Map json) => SourceChange( + message: json['message'] as String, + edits: (json['edits'] as List) + .map((e) => SourceEdit.fromJson(e as Map)) + .toList(), + ); + +Map _$SourceChangeToJson(SourceChange instance) => + { + 'message': instance.message, + 'edits': instance.edits, + }; + +SourceEdit _$SourceEditFromJson(Map json) => SourceEdit( + offset: json['offset'] as int, + length: json['length'] as int, + replacement: json['replacement'] as String, + ); + +Map _$SourceEditToJson(SourceEdit instance) => + { + 'offset': instance.offset, + 'length': instance.length, + 'replacement': instance.replacement, + }; + +DocumentResponse _$DocumentResponseFromJson(Map json) => + DocumentResponse( + dartdoc: json['dartdoc'] as String?, + elementKind: json['elementKind'] as String?, + elementDescription: json['elementDescription'] as String?, + containingLibraryName: json['containingLibraryName'] as String?, + deprecated: json['deprecated'] as bool?, + propagatedType: json['propagatedType'] as String?, + ); + +Map _$DocumentResponseToJson(DocumentResponse instance) => + { + 'dartdoc': instance.dartdoc, + 'elementKind': instance.elementKind, + 'elementDescription': instance.elementDescription, + 'containingLibraryName': instance.containingLibraryName, + 'deprecated': instance.deprecated, + 'propagatedType': instance.propagatedType, + }; + CompleteResponse _$CompleteResponseFromJson(Map json) => CompleteResponse( replacementOffset: json['replacementOffset'] as int, diff --git a/pkgs/dartpad_shared/lib/services.dart b/pkgs/dartpad_shared/lib/services.dart index 7ac82928c..5cefd016d 100644 --- a/pkgs/dartpad_shared/lib/services.dart +++ b/pkgs/dartpad_shared/lib/services.dart @@ -25,13 +25,11 @@ class ServicesClient { Future complete(SourceRequest request) => _requestPost('complete', request.toJson(), CompleteResponse.fromJson); - // TODO: Implement document(). - // Future document(SourceRequest request) => - // _request('document', request.toJson(), DocumentResponse.fromJson); + Future document(SourceRequest request) => + _requestPost('document', request.toJson(), DocumentResponse.fromJson); - // TODO: Implement fixes(). - // Future fixes(SourceRequest request) => - // _request('fixes', request.toJson(), FixesResponse.fromJson); + Future fixes(SourceRequest request) => + _requestPost('fixes', request.toJson(), FixesResponse.fromJson); Future format(SourceRequest request) => _requestPost('format', request.toJson(), FormatResponse.fromJson); @@ -52,18 +50,19 @@ class ServicesClient { ) async { final response = await client.get(Uri.parse('${rootUrl}api/dartservices/v3/$action')); + if (response.statusCode != 200) { throw ApiRequestError( '$action: ${response.statusCode}: ${response.reasonPhrase}', response.body, ); - } - - try { - return responseFactory( - json.decode(response.body) as Map); - } on FormatException catch (e) { - throw ApiRequestError('$action: $e', response.body); + } else { + try { + return responseFactory( + json.decode(response.body) as Map); + } on FormatException catch (e) { + throw ApiRequestError('$action: $e', response.body); + } } } @@ -81,13 +80,13 @@ class ServicesClient { '$action: ${response.statusCode}: ${response.reasonPhrase}', response.body, ); - } - - try { - return responseFactory( - json.decode(response.body) as Map); - } on FormatException catch (e) { - throw ApiRequestError('$action: $e', response.body); + } else { + try { + return responseFactory( + json.decode(response.body) as Map); + } on FormatException catch (e) { + throw ApiRequestError('$action: $e', response.body); + } } } } diff --git a/pkgs/dartpad_shared/pubspec.yaml b/pkgs/dartpad_shared/pubspec.yaml index 1c4fff8b3..b7c9fbb53 100644 --- a/pkgs/dartpad_shared/pubspec.yaml +++ b/pkgs/dartpad_shared/pubspec.yaml @@ -7,8 +7,9 @@ environment: dependencies: http: ^1.1.0 - json_annotation: ^4.8.0 + json_annotation: ^4.8.1 dev_dependencies: build_runner: ^2.4.5 + dart_flutter_team_lints: ^2.0.0 json_serializable: ^6.7.0 diff --git a/pkgs/sketch_pad/lib/main.dart b/pkgs/sketch_pad/lib/main.dart index b864e8f8b..5287458a3 100644 --- a/pkgs/sketch_pad/lib/main.dart +++ b/pkgs/sketch_pad/lib/main.dart @@ -25,12 +25,14 @@ import 'utils.dart'; import 'versions.dart'; import 'widgets.dart'; -// TODO: handle large console content - // TODO: explore using the monaco editor // TODO: have a theme toggle +// TODO: show documentation on hover + +// TODO: support changing the flutter sdk channel + const appName = 'DartPad'; final router = _createRouter(); diff --git a/pkgs/sketch_pad/pubspec.yaml b/pkgs/sketch_pad/pubspec.yaml index 468a7ff35..90d10ba7c 100644 --- a/pkgs/sketch_pad/pubspec.yaml +++ b/pkgs/sketch_pad/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: fluttering_phrases: any go_router: ^10.0.0 http: any - json_annotation: ^4.8.0 + json_annotation: ^4.8.1 pointer_interceptor: ^0.9.0 protobuf: any provider: ^6.0.0