-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: handle completion request (#17)
- Loading branch information
1 parent
aa086c8
commit aada678
Showing
12 changed files
with
581 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import 'package:cli_completion/src/system_shell.dart'; | ||
import 'package:mason_logger/mason_logger.dart'; | ||
import 'package:meta/meta.dart'; | ||
|
||
/// {@template completion_result} | ||
/// Describes the result of a completion handling process. | ||
/// {@endtemplate} | ||
/// | ||
/// Generated after parsing a completion request from the shell, it is | ||
/// responsible to contain the information to be sent back to the shell | ||
/// (via stdout) including suggestions and its metadata (description). | ||
/// | ||
/// See also: | ||
/// - [ValueCompletionResult] | ||
/// - [EmptyCompletionResult] | ||
@immutable | ||
abstract class CompletionResult { | ||
/// Creates a [CompletionResult] that contains predefined suggestions. | ||
const factory CompletionResult.fromMap(Map<String, String?> completions) = | ||
ValueCompletionResult._fromMap; | ||
|
||
const CompletionResult._(); | ||
|
||
/// Render the completion suggestions on the [shell]. | ||
void render(Logger logger, SystemShell shell); | ||
} | ||
|
||
/// {@template value_completion_result} | ||
/// A [CompletionResult] that contains completion suggestions. | ||
/// {@endtemplate} | ||
class ValueCompletionResult extends CompletionResult { | ||
/// {@macro value_completion_result} | ||
ValueCompletionResult() | ||
: _completions = <String, String?>{}, | ||
super._(); | ||
|
||
/// Create a [ValueCompletionResult] with predefined completion suggestions | ||
/// | ||
/// Since this can be const, calling "addSuggestion" on instances created | ||
/// with this constructor may result in runtime exceptions. | ||
/// Use [CompletionResult.fromMap] instead. | ||
const ValueCompletionResult._fromMap(this._completions) : super._(); | ||
|
||
/// A map of completion suggestions to their descriptions. | ||
final Map<String, String?> _completions; | ||
|
||
/// Adds an entry to the current pool of suggestions. Overrides any previous | ||
/// entry with the same [completion]. | ||
void addSuggestion(String completion, [String? description]) { | ||
_completions[completion] = description; | ||
} | ||
|
||
@override | ||
void render(Logger logger, SystemShell shell) { | ||
for (final entry in _completions.entries) { | ||
switch (shell) { | ||
case SystemShell.zsh: | ||
// On zsh, colon acts as delimitation between a suggestion and its | ||
// description. Any literal colon should be escaped. | ||
final suggestion = entry.key.replaceAll(':', r'\:'); | ||
final description = entry.value?.replaceAll(':', r'\:'); | ||
|
||
logger.info( | ||
'$suggestion${description != null ? ':$description' : ''}', | ||
); | ||
break; | ||
case SystemShell.bash: | ||
logger.info(entry.key); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// {@template no_completion_result} | ||
/// A [CompletionResult] that indicates that no completion suggestions should be | ||
/// displayed. | ||
/// {@endtemplate} | ||
class EmptyCompletionResult extends CompletionResult { | ||
/// {@macro no_completion_result} | ||
const EmptyCompletionResult() : super._(); | ||
|
||
@override | ||
void render(Logger logger, SystemShell shell) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:equatable/equatable.dart'; | ||
|
||
import 'package:meta/meta.dart'; | ||
|
||
/// {@template completion_state} | ||
/// A description of the state of a user input when requesting completion. | ||
/// {@endtemplate} | ||
@immutable | ||
class CompletionState extends Equatable { | ||
/// {@macro completion_state} | ||
@visibleForTesting | ||
const CompletionState({ | ||
required this.cword, | ||
required this.cpoint, | ||
required this.cline, | ||
required this.args, | ||
}); | ||
|
||
/// The index of the word being completed | ||
final int cword; | ||
|
||
/// The position of the cursor upon completion request | ||
final int cpoint; | ||
|
||
/// The user prompt that is being completed | ||
final String cline; | ||
|
||
/// The arguments that were passed by the user so far | ||
final Iterable<String> args; | ||
|
||
@override | ||
bool? get stringify => true; | ||
|
||
/// Creates a [CompletionState] from the environment variables set by the | ||
/// shell script. | ||
static CompletionState? fromEnvironment([ | ||
Map<String, String>? environmentOverride, | ||
]) { | ||
final environment = environmentOverride ?? Platform.environment; | ||
final cword = environment['COMP_CWORD']; | ||
final cpoint = environment['COMP_POINT']; | ||
final compLine = environment['COMP_LINE']; | ||
|
||
if (cword == null || cpoint == null || compLine == null) { | ||
return null; | ||
} | ||
|
||
final cwordInt = int.tryParse(cword); | ||
final cpointInt = int.tryParse(cpoint); | ||
|
||
if (cwordInt == null || cpointInt == null) { | ||
return null; | ||
} | ||
|
||
final args = compLine.trimLeft().split(' ').skip(1); | ||
|
||
return CompletionState( | ||
cword: cwordInt, | ||
cpoint: cpointInt, | ||
cline: compLine, | ||
args: args, | ||
); | ||
} | ||
|
||
@override | ||
List<Object?> get props => [cword, cpoint, cline, args]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import 'package:args/args.dart'; | ||
import 'package:cli_completion/cli_completion.dart'; | ||
|
||
/// {@template completion_parser} | ||
/// The workhorse of the completion system. | ||
/// | ||
/// It is responsible for discovering the possible completions given a | ||
/// [CompletionState]. | ||
/// {@endtemplate} | ||
class CompletionParser { | ||
/// {@macro completion_parser} | ||
CompletionParser(this._state); | ||
|
||
final CompletionState _state; | ||
|
||
/// Do not complete if there is an argument terminator in the middle of | ||
/// the sentence | ||
bool _containsArgumentTerminator() { | ||
final args = _state.args; | ||
return args.isNotEmpty && args.take(args.length - 1).contains('--'); | ||
} | ||
|
||
/// Parse the given [CompletionState] into a [CompletionResult] given the | ||
/// structure of commands and options declared by the CLIs [ArgParser]. | ||
CompletionResult parse() { | ||
if (_containsArgumentTerminator()) { | ||
return const EmptyCompletionResult(); | ||
} | ||
|
||
// todo(renancaraujo): actually suggest useful things | ||
return const CompletionResult.fromMap({ | ||
'Brazil': 'A country', | ||
'USA': 'Another country', | ||
'Netherlands': 'Guess what: a country', | ||
'Portugal': 'Yep, a country' | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.