diff --git a/fixtures/completion/html_no_completion.php b/fixtures/completion/html_no_completion.php new file mode 100644 index 00000000..93184183 --- /dev/null +++ b/fixtures/completion/html_no_completion.php @@ -0,0 +1 @@ +< diff --git a/src/CompletionProvider.php b/src/CompletionProvider.php index 68c5a0cc..d774423e 100644 --- a/src/CompletionProvider.php +++ b/src/CompletionProvider.php @@ -10,7 +10,9 @@ Position, CompletionList, CompletionItem, - CompletionItemKind + CompletionItemKind, + CompletionContext, + CompletionTriggerKind }; use Microsoft\PhpParser; use Microsoft\PhpParser\Node; @@ -122,9 +124,10 @@ public function __construct(DefinitionResolver $definitionResolver, ReadableInde * * @param PhpDocument $doc The opened document * @param Position $pos The cursor position + * @param CompletionContext $context The completion context * @return CompletionList */ - public function provideCompletion(PhpDocument $doc, Position $pos): CompletionList + public function provideCompletion(PhpDocument $doc, Position $pos, CompletionContext $context = null): CompletionList { // This can be made much more performant if the tree follows specific invariants. $node = $doc->getNodeAtPosition($pos); @@ -152,7 +155,21 @@ public function provideCompletion(PhpDocument $doc, Position $pos): CompletionLi // Inspect the type of expression under the cursor - if ($node === null || $node instanceof Node\Statement\InlineHtml || $pos == new Position(0, 0)) { + $content = $doc->getContent(); + $offset = $pos->toOffset($content); + if ( + $node === null + || ( + $node instanceof Node\Statement\InlineHtml + && ( + $context === null + // Make sure to not suggest on the > trigger character in HTML + || $context->triggerKind === CompletionTriggerKind::INVOKED + || $context->triggerCharacter === '<' + ) + ) + || $pos == new Position(0, 0) + ) { // HTML, beginning of file // Inside HTML and at the beginning of the file, propose triggerKind = $triggerKind; + $this->triggerCharacter = $triggerCharacter; + } +} diff --git a/src/Protocol/CompletionTriggerKind.php b/src/Protocol/CompletionTriggerKind.php new file mode 100644 index 00000000..d36129dc --- /dev/null +++ b/src/Protocol/CompletionTriggerKind.php @@ -0,0 +1,16 @@ + */ - public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise + public function completion(TextDocumentIdentifier $textDocument, Position $position, CompletionContext $context = null): Promise { - return coroutine(function () use ($textDocument, $position) { + return coroutine(function () use ($textDocument, $position, $context) { $document = yield $this->documentLoader->getOrLoad($textDocument->uri); - return $this->completionProvider->provideCompletion($document, $position); + return $this->completionProvider->provideCompletion($document, $position, $context); }); } diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index 0d68ec3e..424dc3ca 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -17,7 +17,9 @@ Position, CompletionList, CompletionItem, - CompletionItemKind + CompletionItemKind, + CompletionContext, + CompletionTriggerKind }; use function LanguageServer\pathToUri; @@ -464,6 +466,41 @@ public function testHtmlWithPrefix() ], true), $items); } + public function testHtmlPrefixShouldNotTriggerCompletion() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(0, 1), + new CompletionContext(CompletionTriggerKind::TRIGGER_CHARACTER, '>') + )->wait(); + $this->assertEquals(new CompletionList([], true), $items); + } + + public function testHtmlPrefixShouldTriggerCompletionIfManuallyInvoked() + { + $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/html_no_completion.php'); + $this->loader->open($completionUri, file_get_contents($completionUri)); + $items = $this->textDocument->completion( + new TextDocumentIdentifier($completionUri), + new Position(0, 1), + new CompletionContext(CompletionTriggerKind::INVOKED) + )->wait(); + $this->assertEquals(new CompletionList([ + new CompletionItem( + '