diff --git a/mlir/include/mlir/IR/OpImplementation.h b/mlir/include/mlir/IR/OpImplementation.h --- a/mlir/include/mlir/IR/OpImplementation.h +++ b/mlir/include/mlir/IR/OpImplementation.h @@ -572,12 +572,10 @@ virtual ParseResult parseOptionalString(std::string *string) = 0; /// Parse a given keyword. - ParseResult parseKeyword(StringRef keyword, const Twine &msg = "") { - auto loc = getCurrentLocation(); - if (parseOptionalKeyword(keyword)) - return emitError(loc, "expected '") << keyword << "'" << msg; - return success(); + ParseResult parseKeyword(StringRef keyword) { + return parseKeyword(keyword, ""); } + virtual ParseResult parseKeyword(StringRef keyword, const Twine &msg) = 0; /// Parse a keyword into 'keyword'. ParseResult parseKeyword(StringRef *keyword) { diff --git a/mlir/include/mlir/Parser/CodeComplete.h b/mlir/include/mlir/Parser/CodeComplete.h --- a/mlir/include/mlir/Parser/CodeComplete.h +++ b/mlir/include/mlir/Parser/CodeComplete.h @@ -43,6 +43,11 @@ /// completions. virtual void appendBlockCompletion(StringRef name) = 0; + /// Signal a completion for the given expected tokens, which are optional if + /// `optional` is set. + virtual void completeExpectedTokens(ArrayRef tokens, + bool optional) = 0; + protected: /// Create a new code completion context with the given code complete /// location. diff --git a/mlir/lib/Parser/AsmParserImpl.h b/mlir/lib/Parser/AsmParserImpl.h --- a/mlir/lib/Parser/AsmParserImpl.h +++ b/mlir/lib/Parser/AsmParserImpl.h @@ -242,8 +242,21 @@ return success(); } + ParseResult parseKeyword(StringRef keyword, const Twine &msg) override { + if (parser.getToken().isCodeCompletion()) + return parser.codeCompleteExpectedTokens(keyword); + + auto loc = getCurrentLocation(); + if (parseOptionalKeyword(keyword)) + return emitError(loc, "expected '") << keyword << "'" << msg; + return success(); + } + /// Parse the given keyword if present. ParseResult parseOptionalKeyword(StringRef keyword) override { + if (parser.getToken().isCodeCompletion()) + return parser.codeCompleteOptionalTokens(keyword); + // Check that the current token has the same spelling. if (!parser.isCurrentTokenAKeyword() || parser.getTokenSpelling() != keyword) @@ -267,6 +280,9 @@ ParseResult parseOptionalKeyword(StringRef *keyword, ArrayRef allowedKeywords) override { + if (parser.getToken().isCodeCompletion()) + return parser.codeCompleteOptionalTokens(allowedKeywords); + // Check that the current token is a keyword. if (!parser.isCurrentTokenAKeyword()) return failure(); diff --git a/mlir/lib/Parser/Parser.h b/mlir/lib/Parser/Parser.h --- a/mlir/lib/Parser/Parser.h +++ b/mlir/lib/Parser/Parser.h @@ -319,6 +319,8 @@ ParseResult codeCompleteOperationName(StringRef dialectName); ParseResult codeCompleteDialectOrElidedOpName(SMLoc loc); ParseResult codeCompleteStringDialectOrOperationName(StringRef name); + ParseResult codeCompleteExpectedTokens(ArrayRef tokens); + ParseResult codeCompleteOptionalTokens(ArrayRef tokens); protected: /// The Parser is subclassed and reinstantiated. Do not add additional diff --git a/mlir/lib/Parser/Parser.cpp b/mlir/lib/Parser/Parser.cpp --- a/mlir/lib/Parser/Parser.cpp +++ b/mlir/lib/Parser/Parser.cpp @@ -395,6 +395,15 @@ return failure(); } +ParseResult Parser::codeCompleteExpectedTokens(ArrayRef tokens) { + state.codeCompleteContext->completeExpectedTokens(tokens, /*optional=*/false); + return failure(); +} +ParseResult Parser::codeCompleteOptionalTokens(ArrayRef tokens) { + state.codeCompleteContext->completeExpectedTokens(tokens, /*optional=*/true); + return failure(); +} + //===----------------------------------------------------------------------===// // OperationParser //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp --- a/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp +++ b/mlir/lib/Tools/mlir-lsp-server/MLIRServer.cpp @@ -690,6 +690,16 @@ completionList.items.emplace_back(item); } + /// Signal a completion for the given expected token. + void completeExpectedTokens(ArrayRef tokens, bool optional) final { + for (StringRef token : tokens) { + lsp::CompletionItem item(token, lsp::CompletionItemKind::Keyword); + item.sortText = "0"; + item.detail = optional ? "optional" : ""; + completionList.items.emplace_back(item); + } + } + private: lsp::CompletionList &completionList; MLIRContext *ctx; diff --git a/mlir/test/mlir-lsp-server/completion.test b/mlir/test/mlir-lsp-server/completion.test --- a/mlir/test/mlir-lsp-server/completion.test +++ b/mlir/test/mlir-lsp-server/completion.test @@ -100,6 +100,39 @@ // CHECK: ] // CHECK-NEXT: } // ----- +{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{ + "textDocument":{"uri":"test:///foo.mlir"}, + "position":{"line":0,"character":10} +}} +// CHECK: "id": 1 +// CHECK-NEXT: "jsonrpc": "2.0", +// CHECK-NEXT: "result": { +// CHECK-NEXT: "isIncomplete": false, +// CHECK-NEXT: "items": [ +// CHECK-NEXT: { +// CHECK-NEXT: "detail": "optional", +// CHECK-NEXT: "insertTextFormat": 1, +// CHECK-NEXT: "kind": 14, +// CHECK-NEXT: "label": "public", +// CHECK-NEXT: "sortText": "0" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "detail": "optional", +// CHECK-NEXT: "insertTextFormat": 1, +// CHECK-NEXT: "kind": 14, +// CHECK-NEXT: "label": "private", +// CHECK-NEXT: "sortText": "0" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "detail": "optional", +// CHECK-NEXT: "insertTextFormat": 1, +// CHECK-NEXT: "kind": 14, +// CHECK-NEXT: "label": "nested", +// CHECK-NEXT: "sortText": "0" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// ----- {"jsonrpc":"2.0","id":3,"method":"shutdown"} // ----- {"jsonrpc":"2.0","method":"exit"}