blob: 23cf1043fbae41dc34bd6b67b80942047eedd8d4 [file] [log] [blame]
// Copyright (c) 2021, 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:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_selection_ranges.dart'
hide SelectionRange;
import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
typedef StaticOptions =
Either3<bool, SelectionRangeOptions, SelectionRangeRegistrationOptions>;
class SelectionRangeHandler
extends LspMessageHandler<SelectionRangeParams, List<SelectionRange>?> {
SelectionRangeHandler(super.server);
@override
Method get handlesMessage => Method.textDocument_selectionRange;
@override
LspJsonHandler<SelectionRangeParams> get jsonHandler =>
SelectionRangeParams.jsonHandler;
@override
Future<ErrorOr<List<SelectionRange>?>> handle(
SelectionRangeParams params,
MessageInfo message,
CancellationToken token,
) async {
if (!isDartDocument(params.textDocument)) {
return success(null);
}
var path = pathOfDoc(params.textDocument);
return path.mapResult((path) async {
var unit = await requireUnresolvedUnit(path);
return unit.mapResultSync((unit) {
var positions = params.positions;
var offsets =
positions.map((pos) => toOffset(unit.lineInfo, pos)).errorOrResults;
var allRanges = offsets.mapResultSync(
(offsets) => success(_getSelectionRangesForOffsets(offsets, unit)),
);
return allRanges;
});
});
}
SelectionRange _getSelectionRangesForOffset(
CompilationUnit unit,
int offset,
) {
var lineInfo = unit.lineInfo;
var computer = DartSelectionRangeComputer(unit, offset);
var ranges = computer.compute();
// Loop through the items starting at the end (the outermost range), using
// each item as the parent for the next item.
SelectionRange? last;
for (var i = ranges.length - 1; i >= 0; i--) {
var range = ranges[i];
last = SelectionRange(
range: toRange(lineInfo, range.offset, range.length),
parent: last,
);
}
// When there is no range for a given position, return an empty range at the
// requested position. From the LSP spec:
//
// "To allow for results where some positions have selection ranges and
// others do not, result[i].range is allowed to be the empty range at
// positions[i]."
return last ?? SelectionRange(range: toRange(lineInfo, offset, 0));
}
List<SelectionRange> _getSelectionRangesForOffsets(
List<int> offsets,
ParsedUnitResult result,
) {
return offsets
.map((offset) => _getSelectionRangesForOffset(result.unit, offset))
.toList();
}
}
class SelectionRangeRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
SelectionRangeRegistrations(super.info);
@override
ToJsonable? get options =>
SelectionRangeRegistrationOptions(documentSelector: dartFiles);
@override
Method get registrationMethod => Method.textDocument_selectionRange;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.selectionRange;
}