diff --git a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart index 7f45c2f92a8..301c8868375 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart @@ -288,13 +288,6 @@ final class KeywordSuggestion extends CandidateSuggestion { /// should be positioned. final int selectionOffset; - /// Initialize a newly created candidate suggestion to suggest the [keyword]. - factory KeywordSuggestion.fromKeyword({required Keyword keyword}) { - var lexeme = keyword.lexeme; - return KeywordSuggestion._( - completion: lexeme, selectionOffset: lexeme.length); - } - /// Initialize a newly created candidate suggestion to suggest the [keyword]. /// /// If [annotatedText] is provided. The annotated text is used in cases where @@ -306,41 +299,36 @@ final class KeywordSuggestion extends CandidateSuggestion { /// be used as the selection offset. If the text doesn't contain a caret, then /// the insert text will be the annotated text and the selection offset will /// be at the end of the text. - factory KeywordSuggestion.fromKeywordAndText({ - required Keyword? keyword, + factory KeywordSuggestion.fromKeyword({ + required Keyword keyword, required String? annotatedText, }) { - assert(keyword != null || annotatedText != null); - - var completion = ''; - int? selectionOffset; - - if (keyword != null) { - completion = keyword.lexeme; - } + var completion = keyword.lexeme; + var selectionOffset = completion.length; if (annotatedText != null) { - var caretIndex = annotatedText.indexOf('^'); - if (caretIndex < 0) { - completion += annotatedText; - } else { - selectionOffset = completion.length + caretIndex; - completion += annotatedText.substring(0, caretIndex) + - annotatedText.substring(caretIndex + 1); - } + var (rawText, caretIndex) = annotatedText.withoutCaret; + completion += rawText; + selectionOffset += caretIndex ?? rawText.length; } - selectionOffset ??= completion.length; return KeywordSuggestion._( completion: completion, selectionOffset: selectionOffset, ); } - /// Initialize a newly created candidate suggestion to suggest the [keyword]. - factory KeywordSuggestion.fromPseudoKeyword({required String keyword}) { + /// If [annotatedText] contains a caret (`^`), then the completion will use + /// the annotated text with the caret removed and the index of the caret will + /// be used as the selection offset. If the text doesn't contain a caret, then + /// the insert text will be the annotated text and the selection offset will + /// be at the end of the text. + factory KeywordSuggestion.fromText(String annotatedText) { + var (rawText, caretIndex) = annotatedText.withoutCaret; return KeywordSuggestion._( - completion: keyword, selectionOffset: keyword.length); + completion: rawText, + selectionOffset: caretIndex ?? rawText.length, + ); } /// Initialize a newly created candidate suggestion to suggest a keyword. @@ -632,6 +620,18 @@ final class UriSuggestion extends CandidateSuggestion { String get completion => uriStr; } +extension on String { + (String, int?) get withoutCaret { + var caretIndex = indexOf('^'); + if (caretIndex < 0) { + return (this, null); + } else { + var rawText = substring(0, caretIndex) + substring(caretIndex + 1); + return (rawText, caretIndex); + } + } +} + extension SuggestionBuilderExtension on SuggestionBuilder { // TODO(brianwilkerson): Move these to `SuggestionBuilder`, possibly as part // of splitting it into a legacy builder and an LSP builder. diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart index 9d85579f15d..f308eb47725 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart @@ -1064,7 +1064,7 @@ class InScopeCompletionPass extends SimpleAstVisitor { if (name != null && offset <= name.end) { keywordHelper.addKeyword(Keyword.ON); if (featureSet.isEnabled(Feature.inline_class)) { - keywordHelper.addPseudoKeyword('type'); + keywordHelper.addText('type'); } identifierHelper(includePrivateIdentifiers: false).addTopLevelName(); return; @@ -2296,10 +2296,10 @@ class InScopeCompletionPass extends SimpleAstVisitor { void visitSwitchDefault(SwitchDefault node) { if (offset <= node.keyword.offset) { keywordHelper.addKeyword(Keyword.CASE); - keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':'); + keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':'); } else if (offset <= node.keyword.end) { if (node.colon.isSynthetic) { - keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':'); + keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':'); } else { keywordHelper.addKeyword(Keyword.DEFAULT); } @@ -2377,7 +2377,7 @@ class InScopeCompletionPass extends SimpleAstVisitor { collector.completionLocation = 'SwitchMember_statement'; if (node.statements.isEmpty || offset <= node.statements.first.offset) { keywordHelper.addKeyword(Keyword.CASE); - keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':'); + keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':'); } _forStatement(node); } @@ -2397,10 +2397,10 @@ class InScopeCompletionPass extends SimpleAstVisitor { collector.completionLocation = 'SwitchMember_statement'; var members = node.members; keywordHelper.addKeyword(Keyword.CASE); - keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':'); + keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':'); if (members.isNotEmpty) { if (!members.any((element) => element is SwitchDefault)) { - keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':'); + keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':'); } var element = members.elementBefore(offset); if (element != null) { diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart index 5a6037e8b01..73e3a325118 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart @@ -205,16 +205,11 @@ class KeywordHelper { if (before == null && !unit.directives.any((d) => d is LibraryDirective)) { addKeyword(Keyword.LIBRARY); } - addKeywordFromText(Keyword.IMPORT, " '^';"); - addKeywordFromText(Keyword.EXPORT, " '^';"); - addKeywordFromText(Keyword.PART, " '^';"); + addKeywordAndText(Keyword.IMPORT, " '^';"); + addKeywordAndText(Keyword.EXPORT, " '^';"); + addKeywordAndText(Keyword.PART, " '^';"); if (unit.directives.isEmpty) { - collector.addSuggestion( - KeywordSuggestion.fromKeywordAndText( - keyword: null, - annotatedText: "${Keyword.PART.lexeme} ${Keyword.OF.lexeme} '^';", - ), - ); + addText("${Keyword.PART.lexeme} ${Keyword.OF.lexeme} '^';"); } } @@ -326,7 +321,7 @@ class KeywordHelper { if (node.onKeyword.isSynthetic) { addKeyword(Keyword.ON); if (node.name == null && featureSet.isEnabled(Feature.inline_class)) { - addPseudoKeyword('type'); + addText('type'); } } } @@ -430,8 +425,8 @@ class KeywordHelper { if (_isAbsentOrIn(body?.keyword)) { addKeyword(Keyword.ASYNC); if (body is! ExpressionFunctionBody) { - addKeywordFromText(Keyword.ASYNC, '*'); - addKeywordFromText(Keyword.SYNC, '*'); + addKeywordAndText(Keyword.ASYNC, '*'); + addKeywordAndText(Keyword.SYNC, '*'); } } } @@ -446,7 +441,7 @@ class KeywordHelper { if (firstCombinator == null || offset < firstCombinator.offset) { if (deferredKeyword == null) { if (asKeyword == null) { - addKeywordFromText(Keyword.DEFERRED, ' as'); + addKeywordAndText(Keyword.DEFERRED, ' as'); addKeyword(Keyword.AS); addKeyword(Keyword.HIDE); addKeyword(Keyword.SHOW); @@ -473,7 +468,12 @@ class KeywordHelper { /// Add a keyword suggestion to suggest the [keyword]. void addKeyword(Keyword keyword) { - collector.addSuggestion(KeywordSuggestion.fromKeyword(keyword: keyword)); + collector.addSuggestion( + KeywordSuggestion.fromKeyword( + keyword: keyword, + annotatedText: null, + ), + ); } /// Add a keyword suggestion to suggest the [keyword] followed by the @@ -485,9 +485,13 @@ class KeywordHelper { /// be used as the selection offset. If the text doesn't contain a caret, then /// the insert text will be the annotated text and the selection offset will /// be at the end of the text. - void addKeywordFromText(Keyword keyword, String annotatedText) { - collector.addSuggestion(KeywordSuggestion.fromKeywordAndText( - keyword: keyword, annotatedText: annotatedText)); + void addKeywordAndText(Keyword keyword, String annotatedText) { + collector.addSuggestion( + KeywordSuggestion.fromKeyword( + keyword: keyword, + annotatedText: annotatedText, + ), + ); } /// Add the keywords that are appropriate when the selection is in a mixin @@ -538,12 +542,6 @@ class KeywordHelper { addVariablePatternKeywords(); } - /// Add a keyword suggestion to suggest the [keyword]. - void addPseudoKeyword(String keyword) { - collector - .addSuggestion(KeywordSuggestion.fromPseudoKeyword(keyword: keyword)); - } - /// Add the keywords that are appropriate when the selection is at the /// beginning of a statement. The [node] provides context to determine which /// keywords to include. @@ -553,7 +551,7 @@ class KeywordHelper { } else if (node.inAsyncStarOrSyncStarMethodOrFunction) { addKeyword(Keyword.AWAIT); addKeyword(Keyword.YIELD); - addKeywordFromText(Keyword.YIELD, '*'); + addKeywordAndText(Keyword.YIELD, '*'); } if (node.inLoop) { addKeyword(Keyword.BREAK); @@ -585,11 +583,18 @@ class KeywordHelper { addKeyword(Keyword.WHILE); if (node.inAsyncStarOrSyncStarMethodOrFunction) { addKeyword(Keyword.YIELD); - addKeywordFromText(Keyword.YIELD, '*'); + addKeywordAndText(Keyword.YIELD, '*'); } addKeyword(Keyword.LATE); } + /// Add a keyword suggestion to suggest the [annotatedText]. + void addText(String annotatedText) { + collector.addSuggestion( + KeywordSuggestion.fromText(annotatedText), + ); + } + /// Add the keywords that are appropriate when the selection is after the /// end of a `try` statement. [canHaveFinally] indicates whether it's valid to /// suggest a `finally` clause.