[analysis_server] Convert more LSP handlers to work over the legacy protocol
Change-Id: I9bd1f3ffd9bcdb017a4208e4aedcfb7436259fc8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/318700
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
index 00f135c..121dbcf 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
@@ -144,7 +144,7 @@
/// The target returned by this handler will be sent back to the server for
/// incoming/outgoing calls as the user navigates the call hierarchy in the
/// client.
-class PrepareCallHierarchyHandler extends LspMessageHandler<
+class PrepareCallHierarchyHandler extends SharedMessageHandler<
CallHierarchyPrepareParams,
TextDocumentPrepareCallHierarchyResult> with _CallHierarchyUtils {
PrepareCallHierarchyHandler(super.server);
@@ -222,7 +222,7 @@
/// An abstract base class for incoming and outgoing CallHierarchy handlers
/// which perform largely the same task using different LSP classes.
abstract class _AbstractCallHierarchyCallsHandler<P, R, C>
- extends LspMessageHandler<P, R> with _CallHierarchyUtils {
+ extends SharedMessageHandler<P, R> with _CallHierarchyUtils {
_AbstractCallHierarchyCallsHandler(super.server);
/// Gets the appropriate types of calls for this handler.
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
index 4889101..b0918c4 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
@@ -17,7 +17,7 @@
/// to obtain the code to insert when a new color is selected (see
/// [DocumentColorPresentationHandler]).
class DocumentColorHandler
- extends LspMessageHandler<DocumentColorParams, List<ColorInformation>> {
+ extends SharedMessageHandler<DocumentColorParams, List<ColorInformation>> {
DocumentColorHandler(super.server);
@override
Method get handlesMessage => Method.textDocument_documentColor;
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
index 69a2ca1..02863e0 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
@@ -21,7 +21,7 @@
/// using a color picker (in a location returned by textDocument/documentColor)
/// and needs a representation of this color, including the edits to insert it
/// into the source file.
-class DocumentColorPresentationHandler extends LspMessageHandler<
+class DocumentColorPresentationHandler extends SharedMessageHandler<
ColorPresentationParams, List<ColorPresentation>> {
DocumentColorPresentationHandler(super.server);
@override
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
index 9764dc2..11cc219 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
@@ -8,7 +8,7 @@
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
-class DocumentHighlightsHandler extends LspMessageHandler<
+class DocumentHighlightsHandler extends SharedMessageHandler<
TextDocumentPositionParams, List<DocumentHighlight>?> {
DocumentHighlightsHandler(super.server);
@override
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
index 355005f..84f5e15 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
@@ -8,8 +8,8 @@
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
-class FormatOnTypeHandler
- extends LspMessageHandler<DocumentOnTypeFormattingParams, List<TextEdit>?> {
+class FormatOnTypeHandler extends SharedMessageHandler<
+ DocumentOnTypeFormattingParams, List<TextEdit>?> {
FormatOnTypeHandler(super.server);
@override
Method get handlesMessage => Method.textDocument_onTypeFormatting;
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
index 83d04ef..1630647 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
@@ -8,8 +8,8 @@
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
-class FormatRangeHandler
- extends LspMessageHandler<DocumentRangeFormattingParams, List<TextEdit>?> {
+class FormatRangeHandler extends SharedMessageHandler<
+ DocumentRangeFormattingParams, List<TextEdit>?> {
FormatRangeHandler(super.server);
@override
Method get handlesMessage => Method.textDocument_rangeFormatting;
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
index 1ec1ff9..57224d7 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
@@ -9,7 +9,7 @@
import 'package:analysis_server/src/lsp/source_edits.dart';
class FormattingHandler
- extends LspMessageHandler<DocumentFormattingParams, List<TextEdit>?> {
+ extends SharedMessageHandler<DocumentFormattingParams, List<TextEdit>?> {
FormattingHandler(super.server);
@override
Method get handlesMessage => Method.textDocument_formatting;
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_select_range.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart
similarity index 100%
rename from pkg/analysis_server/lib/src/lsp/handlers/handler_select_range.dart
rename to pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
index 85962a4..4cc6807b 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
@@ -33,7 +33,7 @@
import 'package:analysis_server/src/lsp/handlers/handler_inlay_hint.dart';
import 'package:analysis_server/src/lsp/handlers/handler_references.dart';
import 'package:analysis_server/src/lsp/handlers/handler_rename.dart';
-import 'package:analysis_server/src/lsp/handlers/handler_select_range.dart';
+import 'package:analysis_server/src/lsp/handlers/handler_selection_range.dart';
import 'package:analysis_server/src/lsp/handlers/handler_semantic_tokens.dart';
import 'package:analysis_server/src/lsp/handlers/handler_shutdown.dart';
import 'package:analysis_server/src/lsp/handlers/handler_signature_help.dart';
@@ -75,27 +75,18 @@
TextDocumentCloseHandler.new,
CompletionHandler.new,
CompletionResolveHandler.new,
- DocumentColorHandler.new,
- DocumentColorPresentationHandler.new,
SignatureHelpHandler.new,
DefinitionHandler.new,
TypeDefinitionHandler.new,
SuperHandler.new,
ReferencesHandler.new,
ImplementationHandler.new,
- FormattingHandler.new,
- FormatOnTypeHandler.new,
- FormatRangeHandler.new,
- DocumentHighlightsHandler.new,
DocumentSymbolHandler.new,
CodeActionHandler.new,
ExecuteCommandHandler.new,
WorkspaceFoldersHandler.new,
PrepareRenameHandler.new,
RenameHandler.new,
- PrepareCallHierarchyHandler.new,
- IncomingCallHierarchyHandler.new,
- OutgoingCallHierarchyHandler.new,
PrepareTypeHierarchyHandler.new,
TypeHierarchySubtypesHandler.new,
TypeHierarchySupertypesHandler.new,
@@ -129,7 +120,16 @@
/// Generators for handlers that work with any [AnalysisServer].
static const sharedHandlerGenerators =
<_RequestHandlerGenerator<AnalysisServer>>[
+ DocumentColorHandler.new,
+ DocumentColorPresentationHandler.new,
+ DocumentHighlightsHandler.new,
+ FormatOnTypeHandler.new,
+ FormatRangeHandler.new,
+ FormattingHandler.new,
HoverHandler.new,
+ IncomingCallHierarchyHandler.new,
+ OutgoingCallHierarchyHandler.new,
+ PrepareCallHierarchyHandler.new,
];
InitializedStateMessageHandler(
diff --git a/pkg/analysis_server/test/integration/lsp/handle_test.dart b/pkg/analysis_server/test/integration/lsp/handle_test.dart
index 1bbf751..1bd79b6 100644
--- a/pkg/analysis_server/test/integration/lsp/handle_test.dart
+++ b/pkg/analysis_server/test/integration/lsp/handle_test.dart
@@ -21,9 +21,22 @@
});
}
+/// Integration tests for using LSP over the Legacy protocol.
+///
+/// These tests are slow (each test spawns an out-of-process server) so these
+/// tests are intended only to ensure the basic functionality is available and
+/// not to test all handlers/functionality already are covered by LSP tests.
+///
+/// Additional tests (to verify each expected LSP handler is available over
+/// Legacy) are in `test/lsp_over_legacy/` and tests for all handler
+/// functionality are in `test/lsp`.
@reflectiveTest
class LspOverLegacyTest extends AbstractAnalysisServerIntegrationTest
with LspRequestHelpersMixin {
+ late final testFile = sourcePath('lib/test.dart');
+
+ Uri get testFileUri => Uri.file(testFile);
+
@override
Future<T> expectSuccessfulResponseTo<T, R>(
RequestMessage message,
@@ -63,20 +76,30 @@
}
Future<void> test_error_lspHandlerError() async {
- // This file will not be created.
- final testFile = sourcePath('lib/test.dart');
+ // testFile will not be created.
await standardAnalysisSetup();
await analysisFinished;
await expectLater(
- getHover(Uri.file(testFile), Position(character: 0, line: 0)),
+ getHover(testFileUri, Position(character: 0, line: 0)),
throwsA(isResponseError(ServerErrorCodes.InvalidFilePath,
message: 'File does not exist')),
);
}
+ Future<void> test_format() async {
+ const content = 'void main() {}';
+ const expectedContent = 'void main() {}';
+ writeFile(testFile, content);
+ await standardAnalysisSetup();
+ await analysisFinished;
+
+ final edits = await formatDocument(testFileUri);
+ final formattedContents = applyTextEdits(content, edits!);
+ expect(formattedContents.trimRight(), equals(expectedContent));
+ }
+
Future<void> test_hover() async {
- final testFile = sourcePath('lib/test.dart');
final code = TestCode.parse('''
/// This is my class.
class [!A^aa!] {}
@@ -86,7 +109,7 @@
await standardAnalysisSetup();
await analysisFinished;
- final result = await getHover(Uri.file(testFile), code.position.position);
+ final result = await getHover(testFileUri, code.position.position);
expect(result!.range, code.range.range);
_expectMarkdown(
@@ -109,7 +132,6 @@
/// in a way that is not abstracted by (or affected by refactors to) helper
/// methods to ensure this never changes in a way that will affect clients.
Future<void> test_hover_rawProtocol() async {
- final testFile = sourcePath('lib/test.dart');
final code = TestCode.parse('''
/// This is my class.
class [!A^aa!] {}
@@ -134,7 +156,7 @@
'id': '12345',
'method': Method.textDocument_hover.toString(),
'params': {
- "textDocument": {"uri": Uri.file(testFile).toString()},
+ "textDocument": {"uri": testFileUri.toString()},
"position": code.position.position.toJson(),
},
}
diff --git a/pkg/analysis_server/test/lsp/change_verifier.dart b/pkg/analysis_server/test/lsp/change_verifier.dart
index 74947ef..bf44e2c 100644
--- a/pkg/analysis_server/test/lsp/change_verifier.dart
+++ b/pkg/analysis_server/test/lsp/change_verifier.dart
@@ -207,6 +207,38 @@
}
}
+/// An LSP TextEdit with its index, and a comparer to sort them in a way that
+/// can be applied sequentially while preserving expected behaviour.
+class TextEditWithIndex {
+ final int index;
+ final TextEdit edit;
+
+ TextEditWithIndex(this.index, this.edit);
+
+ TextEditWithIndex.fromUnion(
+ this.index, Either3<AnnotatedTextEdit, SnippetTextEdit, TextEdit> edit)
+ : edit = edit.map((e) => e, (e) => e, (e) => e);
+
+ /// Compares two [TextEditWithIndex] to sort them by the order in which they
+ /// can be sequentially applied to a String to match the behaviour of an LSP
+ /// client.
+ static int compare(TextEditWithIndex edit1, TextEditWithIndex edit2) {
+ final end1 = edit1.edit.range.end;
+ final end2 = edit2.edit.range.end;
+
+ // VS Code's implementation of this is here:
+ // https://github.com/microsoft/vscode/blob/856a306d1a9b0879727421daf21a8059e671e3ea/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts#L475
+
+ if (end1.line != end2.line) {
+ return end1.line.compareTo(end2.line) * -1;
+ } else if (end1.character != end2.character) {
+ return end1.character.compareTo(end2.character) * -1;
+ } else {
+ return edit1.index.compareTo(edit2.index) * -1;
+ }
+ }
+}
+
class _Change {
String? content;
final actions = <String>[];
diff --git a/pkg/analysis_server/test/lsp/request_helpers_mixin.dart b/pkg/analysis_server/test/lsp/request_helpers_mixin.dart
index 8ac6ea2..5929d05 100644
--- a/pkg/analysis_server/test/lsp/request_helpers_mixin.dart
+++ b/pkg/analysis_server/test/lsp/request_helpers_mixin.dart
@@ -7,9 +7,14 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/json_parsing.dart';
+import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:collection/collection.dart';
import 'package:test/test.dart' hide expect;
import 'package:test/test.dart' as test show expect;
+import 'change_verifier.dart';
+
/// Helpers to simplify building LSP requests for use in tests.
///
/// The actual sending of requests must be supplied by the implementing class
@@ -21,9 +26,74 @@
mixin LspRequestHelpersMixin {
int _id = 0;
+ final startOfDocPos = Position(line: 0, character: 0);
+
+ final startOfDocRange = Range(
+ start: Position(line: 0, character: 0),
+ end: Position(line: 0, character: 0));
+
/// Whether to include 'clientRequestTime' fields in outgoing messages.
bool includeClientRequestTime = false;
+ String applyTextEdit(String content, TextEdit edit) {
+ final startPos = edit.range.start;
+ final endPos = edit.range.end;
+ final lineInfo = LineInfo.fromContent(content);
+ final start = lineInfo.getOffsetOfLine(startPos.line) + startPos.character;
+ final end = lineInfo.getOffsetOfLine(endPos.line) + endPos.character;
+ return content.replaceRange(start, end, edit.newText);
+ }
+
+ String applyTextEdits(String content, List<TextEdit> changes) {
+ // Complex text manipulations are described with an array of TextEdit's,
+ // representing a single change to the document.
+ //
+ // All text edits ranges refer to positions in the original document. Text
+ // edits ranges must never overlap, that means no part of the original
+ // document must be manipulated by more than one edit. It is possible
+ // that multiple edits have the same start position (eg. multiple inserts in
+ // reverse order), however since that involves complicated tracking and we
+ // only apply edits here sequentially, we don't supported them. We do sort
+ // edits to ensure we apply the later ones first, so we can assume the locations
+ // in the edit are still valid against the new string as each edit is applied.
+
+ /// Ensures changes are simple enough to apply easily without any complicated
+ /// logic.
+ void validateChangesCanBeApplied() {
+ /// Check if a position is before (but not equal) to another position.
+ bool isBeforeOrEqual(Position p, Position other) =>
+ p.line < other.line ||
+ (p.line == other.line && p.character <= other.character);
+
+ /// Check if a position is after (but not equal) to another position.
+ bool isAfterOrEqual(Position p, Position other) =>
+ p.line > other.line ||
+ (p.line == other.line && p.character >= other.character);
+ // Check if two ranges intersect.
+ bool rangesIntersect(Range r1, Range r2) {
+ var endsBefore = isBeforeOrEqual(r1.end, r2.start);
+ var startsAfter = isAfterOrEqual(r1.start, r2.end);
+ return !(endsBefore || startsAfter);
+ }
+
+ for (final change1 in changes) {
+ for (final change2 in changes) {
+ if (change1 != change2 &&
+ rangesIntersect(change1.range, change2.range)) {
+ throw 'Test helper applyTextEdits does not support applying multiple edits '
+ 'where the edits are not in reverse order.';
+ }
+ }
+ }
+ }
+
+ validateChangesCanBeApplied();
+
+ final indexedEdits = changes.mapIndexed(TextEditWithIndex.new).toList();
+ indexedEdits.sort(TextEditWithIndex.compare);
+ return indexedEdits.map((e) => e.edit).fold(content, applyTextEdit);
+ }
+
Future<List<CallHierarchyIncomingCall>?> callHierarchyIncoming(
CallHierarchyItem item) {
final request = makeRequest(
@@ -44,6 +114,12 @@
request, _fromJsonList(CallHierarchyOutgoingCall.fromJson));
}
+ /// Gets the entire range for [code].
+ Range entireRange(String code) => Range(
+ start: startOfDocPos,
+ end: positionFromOffset(code.length, code),
+ );
+
void expect(Object? actual, Matcher matcher, {String? reason}) =>
test.expect(actual, matcher, reason: reason);
@@ -471,6 +547,11 @@
);
}
+ Position positionFromOffset(int offset, String contents) {
+ final lineInfo = LineInfo.fromContent(contents);
+ return toPosition(lineInfo.getLocation(offset));
+ }
+
Future<List<CallHierarchyItem>?> prepareCallHierarchy(Uri uri, Position pos) {
final request = makeRequest(
Method.textDocument_prepareCallHierarchy,
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 43b46e0..e1190b5 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -10,13 +10,11 @@
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/json_parsing.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
-import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/utilities/mocks.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
-import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
@@ -915,10 +913,6 @@
analysisOptionsPath;
late Uri projectFolderUri, mainFileUri, pubspecFileUri, analysisOptionsUri;
final String simplePubspecContent = 'name: my_project';
- final startOfDocPos = Position(line: 0, character: 0);
- final startOfDocRange = Range(
- start: Position(line: 0, character: 0),
- end: Position(line: 0, character: 0));
/// The client capabilities sent to the server during initialization.
///
@@ -968,65 +962,6 @@
Stream<Message> get serverToClient;
- String applyTextEdit(String content, TextEdit edit) {
- final startPos = edit.range.start;
- final endPos = edit.range.end;
- final lineInfo = LineInfo.fromContent(content);
- final start = lineInfo.getOffsetOfLine(startPos.line) + startPos.character;
- final end = lineInfo.getOffsetOfLine(endPos.line) + endPos.character;
- return content.replaceRange(start, end, edit.newText);
- }
-
- String applyTextEdits(String content, List<TextEdit> changes) {
- // Complex text manipulations are described with an array of TextEdit's,
- // representing a single change to the document.
- //
- // All text edits ranges refer to positions in the original document. Text
- // edits ranges must never overlap, that means no part of the original
- // document must be manipulated by more than one edit. It is possible
- // that multiple edits have the same start position (eg. multiple inserts in
- // reverse order), however since that involves complicated tracking and we
- // only apply edits here sequentially, we don't supported them. We do sort
- // edits to ensure we apply the later ones first, so we can assume the locations
- // in the edit are still valid against the new string as each edit is applied.
-
- /// Ensures changes are simple enough to apply easily without any complicated
- /// logic.
- void validateChangesCanBeApplied() {
- /// Check if a position is before (but not equal) to another position.
- bool isBeforeOrEqual(Position p, Position other) =>
- p.line < other.line ||
- (p.line == other.line && p.character <= other.character);
-
- /// Check if a position is after (but not equal) to another position.
- bool isAfterOrEqual(Position p, Position other) =>
- p.line > other.line ||
- (p.line == other.line && p.character >= other.character);
- // Check if two ranges intersect.
- bool rangesIntersect(Range r1, Range r2) {
- var endsBefore = isBeforeOrEqual(r1.end, r2.start);
- var startsAfter = isAfterOrEqual(r1.start, r2.end);
- return !(endsBefore || startsAfter);
- }
-
- for (final change1 in changes) {
- for (final change2 in changes) {
- if (change1 != change2 &&
- rangesIntersect(change1.range, change2.range)) {
- throw 'Test helper applyTextEdits does not support applying multiple edits '
- 'where the edits are not in reverse order.';
- }
- }
- }
- }
-
- validateChangesCanBeApplied();
-
- final indexedEdits = changes.mapIndexed(TextEditWithIndex.new).toList();
- indexedEdits.sort(TextEditWithIndex.compare);
- return indexedEdits.map((e) => e.edit).fold(content, applyTextEdit);
- }
-
Future<void> changeFile(
int newVersion,
Uri uri,
@@ -1066,12 +1001,6 @@
await sendNotificationToServer(notification);
}
- /// Gets the entire range for [code].
- Range entireRange(String code) => Range(
- start: startOfDocPos,
- end: positionFromOffset(code.length, code),
- );
-
Future<Object?> executeCodeAction(
Either2<Command, CodeAction> codeAction) async {
final command = codeAction.map(
@@ -1404,9 +1333,9 @@
Position positionFromMarker(String contents) =>
positionFromOffset(withoutRangeMarkers(contents).indexOf('^'), contents);
+ @override
Position positionFromOffset(int offset, String contents) {
- final lineInfo = LineInfo.fromContent(withoutMarkers(contents));
- return toPosition(lineInfo.getLocation(offset));
+ return super.positionFromOffset(offset, withoutMarkers(contents));
}
/// Calls the supplied function and responds to any `workspace/configuration`
@@ -1807,33 +1736,3 @@
notification.method == Method.window_showMessage;
}
}
-
-class TextEditWithIndex {
- final int index;
- final TextEdit edit;
-
- TextEditWithIndex(this.index, this.edit);
-
- TextEditWithIndex.fromUnion(
- this.index, Either3<AnnotatedTextEdit, SnippetTextEdit, TextEdit> edit)
- : edit = edit.map((e) => e, (e) => e, (e) => e);
-
- /// Compares two [TextEditWithIndex] to sort them by the order in which they
- /// can be sequentially applied to a String to match the behaviour of an LSP
- /// client.
- static int compare(TextEditWithIndex edit1, TextEditWithIndex edit2) {
- final end1 = edit1.edit.range.end;
- final end2 = edit2.edit.range.end;
-
- // VS Code's implementation of this is here:
- // https://github.com/microsoft/vscode/blob/856a306d1a9b0879727421daf21a8059e671e3ea/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts#L475
-
- if (end1.line != end2.line) {
- return end1.line.compareTo(end2.line) * -1;
- } else if (end1.character != end2.character) {
- return end1.character.compareTo(end2.character) * -1;
- } else {
- return edit1.index.compareTo(edit2.index) * -1;
- }
- }
-}
diff --git a/pkg/analysis_server/test/lsp_over_legacy/call_hierarchy_test.dart b/pkg/analysis_server/test/lsp_over_legacy/call_hierarchy_test.dart
new file mode 100644
index 0000000..c122ae2
--- /dev/null
+++ b/pkg/analysis_server/test/lsp_over_legacy/call_hierarchy_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2023, 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 'package:analyzer/src/test_utilities/test_code_format.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../utils/test_code_extensions.dart';
+import 'abstract_lsp_over_legacy.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(CallHierarchyTest);
+ });
+}
+
+@reflectiveTest
+class CallHierarchyTest extends LspOverLegacyTest {
+ Future<void> test_incoming() async {
+ final content = '''
+String f^() => 'f';
+String g() => [!f!]();
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final prepareResults = await prepareCallHierarchy(
+ testFileUri,
+ code.position.position,
+ );
+ final prepareResult = prepareResults!.single;
+
+ final incomingResults = await callHierarchyIncoming(prepareResult);
+ final incomingResult = incomingResults!.single;
+
+ expect(incomingResult.from.name, 'g');
+ expect(incomingResult.fromRanges, code.ranges.ranges);
+ }
+
+ Future<void> test_outgoing() async {
+ final content = '''
+String f() => 'f';
+String g^() => [!f!]();
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final prepareResults = await prepareCallHierarchy(
+ testFileUri,
+ code.position.position,
+ );
+ final prepareResult = prepareResults!.single;
+
+ final outgoingResults = await callHierarchyOutgoing(prepareResult);
+ final outgoingResult = outgoingResults!.single;
+
+ expect(outgoingResult.to.name, 'f');
+ expect(outgoingResult.fromRanges, code.ranges.ranges);
+ }
+
+ Future<void> test_prepare() async {
+ final content = '''
+void f^() {}
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final results = await prepareCallHierarchy(
+ testFileUri,
+ code.position.position,
+ );
+ final result = results!.single;
+
+ expect(result.name, 'f');
+ }
+}
diff --git a/pkg/analysis_server/test/lsp_over_legacy/document_color_test.dart b/pkg/analysis_server/test/lsp_over_legacy/document_color_test.dart
new file mode 100644
index 0000000..df0a41d
--- /dev/null
+++ b/pkg/analysis_server/test/lsp_over_legacy/document_color_test.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, 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 'package:analyzer/src/test_utilities/test_code_format.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../utils/test_code_extensions.dart';
+import 'abstract_lsp_over_legacy.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(DocumentColorTest);
+ });
+}
+
+@reflectiveTest
+class DocumentColorTest extends LspOverLegacyTest {
+ @override
+ Future<void> setUp() async {
+ await super.setUp();
+ writeTestPackageConfig(flutter: true);
+ }
+
+ Future<void> test_color() async {
+ final content = '''
+import 'package:flutter/material.dart';
+
+const red = [!Colors.red!];
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final results = await getDocumentColors(testFileUri);
+ final result = results.single;
+
+ expect(result.color.alpha, 1);
+ expect(result.color.red, 1);
+ expect(result.color.green, 0);
+ expect(result.color.blue, 0);
+ expect(result.range, code.range.range);
+ }
+
+ Future<void> test_presentation() async {
+ final content = '''
+import 'package:flutter/material.dart';
+
+const red = [!Colors.red!];
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final colorResults = await getDocumentColors(testFileUri);
+ final colorResult = colorResults.single;
+
+ final colors = await getColorPresentation(
+ testFileUri, code.range.range, colorResult.color);
+ expect(
+ colors.map((c) => c.label),
+ containsAll([
+ 'Color.fromARGB(255, 255, 0, 0)',
+ 'Color.fromRGBO(255, 0, 0, 1.0)',
+ 'Color(0xFFFF0000)',
+ ]),
+ );
+ }
+}
diff --git a/pkg/analysis_server/test/lsp_over_legacy/document_highlights_test.dart b/pkg/analysis_server/test/lsp_over_legacy/document_highlights_test.dart
new file mode 100644
index 0000000..e8454151
--- /dev/null
+++ b/pkg/analysis_server/test/lsp_over_legacy/document_highlights_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, 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 'package:analyzer/src/test_utilities/test_code_format.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../utils/test_code_extensions.dart';
+import 'abstract_lsp_over_legacy.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(DocumentHighlightsTest);
+ });
+}
+
+@reflectiveTest
+class DocumentHighlightsTest extends LspOverLegacyTest {
+ Future<void> test_highlights() async {
+ final content = '''
+var ^a = '';
+void f() {
+ a = '';
+ print(a);
+}
+''';
+ final code = TestCode.parse(content);
+ newFile(testFilePath, code.code);
+ final results =
+ await getDocumentHighlights(testFileUri, code.position.position);
+ expect(results, hasLength(3));
+ }
+}
diff --git a/pkg/analysis_server/test/lsp_over_legacy/format_test.dart b/pkg/analysis_server/test/lsp_over_legacy/format_test.dart
new file mode 100644
index 0000000..aa534dc0
--- /dev/null
+++ b/pkg/analysis_server/test/lsp_over_legacy/format_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, 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 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'abstract_lsp_over_legacy.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(FormatTest);
+ });
+}
+
+@reflectiveTest
+class FormatTest extends LspOverLegacyTest {
+ Future<void> test_format() async {
+ const content = 'void main() {}';
+ const expectedContent = 'void main() {}';
+ newFile(testFilePath, content);
+ await waitForTasksFinished();
+
+ final edits = await formatDocument(testFileUri);
+ final formattedContents = applyTextEdits(content, edits!);
+ expect(formattedContents.trimRight(), equals(expectedContent));
+ }
+
+ Future<void> test_formatOnType() async {
+ const content = 'void main() {}';
+ const expectedContent = 'void main() {}';
+ newFile(testFilePath, content);
+ await waitForTasksFinished();
+
+ final edits = await formatOnType(testFileUri, startOfDocPos, '}');
+ final formattedContents = applyTextEdits(content, edits!);
+ expect(formattedContents.trimRight(), equals(expectedContent));
+ }
+
+ Future<void> test_formatRange() async {
+ const content = 'void main() {}';
+ const expectedContent = 'void main() {}';
+ newFile(testFilePath, content);
+ await waitForTasksFinished();
+
+ final edits = await formatRange(testFileUri, entireRange(content));
+ final formattedContents = applyTextEdits(content, edits!);
+ expect(formattedContents.trimRight(), equals(expectedContent));
+ }
+}
diff --git a/pkg/analysis_server/test/lsp_over_legacy/test_all.dart b/pkg/analysis_server/test/lsp_over_legacy/test_all.dart
index a398398..4a320c7 100644
--- a/pkg/analysis_server/test/lsp_over_legacy/test_all.dart
+++ b/pkg/analysis_server/test/lsp_over_legacy/test_all.dart
@@ -4,10 +4,18 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'call_hierarchy_test.dart' as call_hierarchy;
+import 'document_color_test.dart' as document_color;
+import 'document_highlights_test.dart' as document_highlights;
+import 'format_test.dart' as format;
import 'hover_test.dart' as hover;
void main() {
defineReflectiveSuite(() {
+ call_hierarchy.main();
+ document_color.main;
+ document_highlights.main();
+ format.main();
hover.main();
}, name: 'lsp_over_legacy');
}
diff --git a/pkg/analysis_server/test/utils/test_code_extensions.dart b/pkg/analysis_server/test/utils/test_code_extensions.dart
index 799307b..6948351 100644
--- a/pkg/analysis_server/test/utils/test_code_extensions.dart
+++ b/pkg/analysis_server/test/utils/test_code_extensions.dart
@@ -7,6 +7,20 @@
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
+extension ListTestCodePositionExtension on List<TestCodePosition> {
+ /// Return the LSP [Position]s of the markers.
+ ///
+ /// Positions are based on [TestCode.code], with all parsed markers removed.
+ List<Position> get positions => map((position) => position.position).toList();
+}
+
+extension ListTestCodeRangeExtension on List<TestCodeRange> {
+ /// The LSP [Range]s indicated by the markers.
+ ///
+ /// Ranges are based on [TestCode.code], with all parsed markers removed.
+ List<Range> get ranges => map((range) => range.range).toList();
+}
+
extension TestCodePositionExtension on TestCodePosition {
/// Return the LSP [Position] of the marker.
///