| // Copyright (c) 2014, 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. |
| |
| library services.src.correction.selection_analyzer; |
| |
| import 'package:analysis_server/src/services/correction/source_range.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| |
| /** |
| * A visitor for visiting [AstNode]s covered by a selection [SourceRange]. |
| */ |
| class SelectionAnalyzer extends GeneralizingAstVisitor<Object> { |
| final SourceRange selection; |
| |
| AstNode _coveringNode; |
| List<AstNode> _selectedNodes; |
| |
| SelectionAnalyzer(this.selection); |
| |
| /** |
| * Return the [AstNode] with the shortest length which completely covers the |
| * specified selection. |
| */ |
| AstNode get coveringNode => _coveringNode; |
| |
| /** |
| * Returns the first selected [AstNode], may be `null`. |
| */ |
| AstNode get firstSelectedNode { |
| if (_selectedNodes == null || _selectedNodes.isEmpty) { |
| return null; |
| } |
| return _selectedNodes[0]; |
| } |
| |
| /** |
| * Returns `true` if there are [AstNode]s fully covered by the |
| * selection [SourceRange]. |
| */ |
| bool get hasSelectedNodes => |
| _selectedNodes != null && !_selectedNodes.isEmpty; |
| |
| /** |
| * Returns `true` if there was no selected nodes yet. |
| */ |
| bool get isFirstNode => _selectedNodes == null; |
| |
| /** |
| * Returns the last selected [AstNode], may be `null`. |
| */ |
| AstNode get lastSelectedNode { |
| if (_selectedNodes == null || _selectedNodes.isEmpty) { |
| return null; |
| } |
| return _selectedNodes[_selectedNodes.length - 1]; |
| } |
| |
| /** |
| * Returns the [SourceRange] which covers selected [AstNode]s, may be `null` |
| * if there are no [AstNode]s under the selection. |
| */ |
| SourceRange get selectedNodeRange { |
| if (_selectedNodes == null || _selectedNodes.isEmpty) { |
| return null; |
| } |
| AstNode firstNode = _selectedNodes[0]; |
| AstNode lastNode = _selectedNodes[_selectedNodes.length - 1]; |
| return rangeStartEnd(firstNode, lastNode); |
| } |
| |
| /** |
| * Return the [AstNode]s fully covered by the selection [SourceRange]. |
| */ |
| List<AstNode> get selectedNodes { |
| if (_selectedNodes == null || _selectedNodes.isEmpty) { |
| return []; |
| } |
| return _selectedNodes; |
| } |
| |
| /** |
| * Adds first selected [AstNode]. |
| */ |
| void handleFirstSelectedNode(AstNode node) { |
| _selectedNodes = []; |
| _selectedNodes.add(node); |
| } |
| |
| /** |
| * Adds second or more selected [AstNode]. |
| */ |
| void handleNextSelectedNode(AstNode node) { |
| if (firstSelectedNode.parent == node.parent) { |
| _selectedNodes.add(node); |
| } |
| } |
| |
| /** |
| * Notifies that selection ends in given [AstNode]. |
| */ |
| void handleSelectionEndsIn(AstNode node) {} |
| |
| /** |
| * Notifies that selection starts in given [AstNode]. |
| */ |
| void handleSelectionStartsIn(AstNode node) {} |
| |
| /** |
| * Resets selected nodes. |
| */ |
| void reset() { |
| _selectedNodes = null; |
| } |
| |
| @override |
| Object visitNode(AstNode node) { |
| SourceRange nodeRange = rangeNode(node); |
| if (selection.covers(nodeRange)) { |
| if (isFirstNode) { |
| handleFirstSelectedNode(node); |
| } else { |
| handleNextSelectedNode(node); |
| } |
| return null; |
| } else if (selection.coveredBy(nodeRange)) { |
| _coveringNode = node; |
| node.visitChildren(this); |
| return null; |
| } else if (selection.startsIn(nodeRange)) { |
| handleSelectionStartsIn(node); |
| node.visitChildren(this); |
| return null; |
| } else if (selection.endsIn(nodeRange)) { |
| handleSelectionEndsIn(node); |
| node.visitChildren(this); |
| return null; |
| } |
| // no intersection |
| return null; |
| } |
| } |