| // 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:analyzer/dart/ast/ast.dart'; | |
| /// Computes selection ranges for a specific offset of a Dart [CompilationUnit]. | |
| /// | |
| /// Select ranges support IDE functionality for "expand range" to increase the | |
| /// selection based on the syntax node of the language. | |
| class DartSelectionRangeComputer { | |
| final CompilationUnit _unit; | |
| final int _offset; | |
| final _selectionRanges = <SelectionRange>[]; | |
| new(this._unit, this._offset); | |
| /// Returns selection ranges for nodes containing [_offset], starting with the | |
| /// closest working up to the outer-most node. | |
| List<SelectionRange> compute() { | |
| var node = _unit.nodeCovering(offset: _offset); | |
| if (node == null) { | |
| return []; | |
| } | |
| while (node != null && node != _unit) { | |
| _recordRange(node); | |
| node = node.parent; | |
| } | |
| return _selectionRanges; | |
| } | |
| int? _formalParameterEndWithoutDefault(FormalParameter node) { | |
| if (node.functionTypedSuffix case var functionTypedSuffix?) { | |
| return functionTypedSuffix.end; | |
| } | |
| if (node.name case var name?) { | |
| return name.end; | |
| } | |
| if (node.type case var type?) { | |
| return type.end; | |
| } | |
| if (node.constFinalOrVarKeyword case var constFinalOrVarKeyword?) { | |
| return constFinalOrVarKeyword.end; | |
| } | |
| if (node.requiredKeyword case var requiredKeyword?) { | |
| return requiredKeyword.end; | |
| } | |
| if (node.covariantKeyword case var covariantKeyword?) { | |
| return covariantKeyword.end; | |
| } | |
| return null; | |
| } | |
| void _recordOffsetLength(int offset, int length) { | |
| // Ignore this node if its range is the same as the last one. | |
| if (_selectionRanges.isNotEmpty) { | |
| var last = _selectionRanges.last; | |
| if (offset == last.offset && length == last.length) { | |
| return; | |
| } | |
| } | |
| _selectionRanges.add(SelectionRange(offset, length)); | |
| } | |
| /// Record the range for [node] if it is not the same as the last-recorded | |
| /// range. | |
| void _recordRange(AstNode node) { | |
| // Ignore certain kinds of nodes. | |
| if (node is NameWithTypeParameters) { | |
| return; | |
| } | |
| if (node case FormalParameter(defaultClause: var defaultClause?) | |
| when _offset < defaultClause.offset) { | |
| var end = _formalParameterEndWithoutDefault(node); | |
| if (end != null) { | |
| _recordOffsetLength(node.offset, end - node.offset); | |
| } | |
| } | |
| if (node is NamedArgument && _offset <= node.colon.end) { | |
| _recordOffsetLength(node.name.offset, node.name.length); | |
| _recordOffsetLength(node.name.offset, node.colon.end - node.name.offset); | |
| } else if (node is RecordLiteralNamedField && _offset <= node.colon.end) { | |
| _recordOffsetLength(node.name.offset, node.name.length); | |
| _recordOffsetLength(node.name.offset, node.colon.end - node.name.offset); | |
| } | |
| _recordOffsetLength(node.offset, node.length); | |
| } | |
| } | |
| class SelectionRange { | |
| final int offset; | |
| final int length; | |
| new(this.offset, this.length); | |
| } |