| // 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:analyzer/file_system/file_system.dart'; |
| import 'package:meta/meta.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 paths will be posix paths, but we might |
| // want to support platform-specific paths at some point, in which case we |
| // should add a flag to the constructor and use that to choose between the |
| // hard-coded `path.posix` and `provider.pathContext`; |
| var provider = request.resourceProvider; |
| 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); |
| } |
| 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; |
| if (child is Folder) { |
| if (!name.startsWith('.')) { |
| yield identifier(name); |
| } |
| } else if (child is File) { |
| yield identifier(name); |
| } |
| } |
| } 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]. |
| 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) => CompletionSuggestion( |
| CompletionSuggestionKind.IDENTIFIER, |
| 1000, |
| identifier, |
| identifier.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 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}); |
| } |