| // Copyright (c) 2020, 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/src/protocol_server.dart'; |
| import 'package:analysis_server/src/services/pub/pub_package_service.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:path/path.dart' as path; |
| |
| /// An object that represents the location of a Boolean value. |
| class BooleanProducer extends Producer { |
| /// Initialize a location whose valid values are Booleans. |
| const BooleanProducer(); |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| yield identifier('true'); |
| yield identifier('false'); |
| } |
| } |
| |
| /// An object that represents the location of an arbitrary value. They serve as |
| /// placeholders when there are no reasonable suggestions for a given location. |
| class EmptyProducer extends Producer { |
| /// Initialize a location whose valid values are arbitrary. |
| const EmptyProducer(); |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| // Returns nothing. |
| } |
| } |
| |
| /// An object that represents the location of a value from a finite set of |
| /// choices. |
| class EnumProducer extends Producer { |
| /// The list of valid values at this location. |
| final List<String> values; |
| |
| /// Initialize a location whose valid values are in the list of [values]. |
| const EnumProducer(this.values); |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| for (var value in values) { |
| yield identifier(value); |
| } |
| } |
| } |
| |
| /// An object that represents the location of a possibly relative file path. |
| class FilePathProducer extends Producer { |
| /// Initialize a producer whose valid values are file paths. |
| const FilePathProducer(); |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| // |
| // This currently assumes that all of the paths in the assets section will |
| // be posix paths. |
| // |
| var context = path.posix; |
| var separator = context.separator; |
| var precedingText = request.precedingText; |
| |
| String parentDirectory; |
| if (precedingText.isEmpty || precedingText.endsWith(separator)) { |
| parentDirectory = precedingText; |
| } else { |
| parentDirectory = context.dirname(precedingText); |
| } |
| if (parentDirectory == '.') { |
| parentDirectory = ''; |
| } else if (parentDirectory.endsWith(separator)) { |
| parentDirectory = parentDirectory.substring( |
| 0, parentDirectory.length - separator.length); |
| } |
| // |
| // Convert from posix to the platform context. |
| // |
| var provider = request.resourceProvider; |
| context = provider.pathContext; |
| parentDirectory = context.joinAll(path.posix.split(parentDirectory)); |
| // |
| // Resolve the relative path and access the disk to see what child entities |
| // exist within the [parentDirectory] that can be suggested. |
| // |
| if (context.isRelative(parentDirectory)) { |
| parentDirectory = |
| context.join(context.dirname(request.filePath), parentDirectory); |
| } |
| parentDirectory = context.normalize(parentDirectory); |
| var dir = provider.getResource(parentDirectory); |
| if (dir is Folder) { |
| try { |
| for (var child in dir.getChildren()) { |
| var name = child.shortName; |
| var relevance = name.startsWith('.') ? 500 : 1000; |
| yield identifier(name, relevance: relevance); |
| } |
| } on FileSystemException { |
| // Guard against I/O exceptions. |
| } |
| } |
| } |
| } |
| |
| /// An object that represents the location of the keys/values in a map. |
| abstract class KeyValueProducer extends Producer { |
| /// Initialize a producer representing a key/value pair in a map. |
| const KeyValueProducer(); |
| |
| /// Returns a producer for values of the given [key], or `null` if there is |
| /// no registered producer for the [key]. |
| Producer? producerForKey(String key); |
| } |
| |
| /// An object that represents the location of an element in a list. |
| class ListProducer extends Producer { |
| /// The producer used to produce suggestions for an element of the list. |
| final Producer element; |
| |
| /// Initialize a location whose valid values are determined by the [element] |
| /// producer. |
| const ListProducer(this.element); |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| for (var suggestion in element.suggestions(request)) { |
| // TODO(brianwilkerson) Consider prepending the suggestion with a hyphen |
| // when the current node isn't already preceded by a hyphen. The |
| // cleanest way to do this is probably to access the [element] producer |
| // in the place where we're choosing a producer in that situation. |
| // suggestion.completion = '- ${suggestion.completion}'; |
| yield suggestion; |
| } |
| } |
| } |
| |
| /// An object that represents the location of the keys in a map. |
| class MapProducer extends KeyValueProducer { |
| /// A table from the value of a key to the producer used to make suggestions |
| /// for the value following the key. |
| final Map<String, Producer> _children; |
| |
| /// Initialize a location whose valid values are the keys of a map as encoded |
| /// by the map of [children]. |
| const MapProducer(this._children); |
| |
| @override |
| Producer? producerForKey(String key) => _children[key]; |
| |
| @override |
| Iterable<CompletionSuggestion> suggestions( |
| YamlCompletionRequest request) sync* { |
| for (var entry in _children.entries) { |
| if (entry.value is ListProducer) { |
| yield identifier('${entry.key}:'); |
| } else { |
| yield identifier('${entry.key}: '); |
| } |
| } |
| } |
| } |
| |
| /// An object that represents a specific location in the structure of the valid |
| /// YAML representation and can produce completion suggestions appropriate for |
| /// that location. |
| abstract class Producer { |
| /// Initialize a newly created instance of this class. |
| const Producer(); |
| |
| /// A utility method used to create a suggestion for the [identifier]. |
| CompletionSuggestion identifier(String identifier, |
| {int relevance = 1000, String? docComplete}) => |
| CompletionSuggestion(CompletionSuggestionKind.IDENTIFIER, relevance, |
| identifier, identifier.length, 0, false, false, |
| docComplete: docComplete); |
| |
| /// A utility method used to create a suggestion for the package [packageName]. |
| CompletionSuggestion packageName(String packageName, |
| {int relevance = 1000}) => |
| CompletionSuggestion(CompletionSuggestionKind.PACKAGE_NAME, relevance, |
| packageName, packageName.length, 0, false, false); |
| |
| /// Return the completion suggestions appropriate to this location. |
| Iterable<CompletionSuggestion> suggestions(YamlCompletionRequest request); |
| } |
| |
| /// The information provided to a [Producer] when requesting completions. |
| class YamlCompletionRequest { |
| /// The resource provider used to access the file system. |
| final ResourceProvider resourceProvider; |
| |
| /// The Pub package service used for looking up package names/versions. |
| final PubPackageService? pubPackageService; |
| |
| /// The absolute path of the file in which completions are being requested. |
| final String filePath; |
| |
| /// The text to the left of the cursor. |
| final String precedingText; |
| |
| /// Initialize a newly created completion request. |
| YamlCompletionRequest( |
| {required this.filePath, |
| required this.precedingText, |
| required this.resourceProvider, |
| required this.pubPackageService}); |
| } |