Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ This extension provides syntax highlighting and code completion for DRL files.
- Syntax highlighting
- Code completion

## How to build

Under `client` directory, run:

```bash
npm install
npm run pack:dev
```

vsix file will be generated in `dist` directory.

## Known Issues

- Code completion may suggest words that are not valid in the current context
Expand Down
14 changes: 13 additions & 1 deletion drools-completion/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,17 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>
-Djava.util.logging.config.file=${project.basedir}/src/test/resources/logging.properties
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
*/
package org.drools.completion;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.vmware.antlr4c3.CodeCompletionCore;
import org.drools.drl.parser.antlr4.DRL10Lexer;
import org.drools.drl.parser.antlr4.DRL10Parser;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
Expand All @@ -31,6 +35,15 @@

public class DRLCompletionHelper {

// PREFERRED_RULES is used to filter out the rules that consist of unwanted tokens
// additionally, it can be used to customize getCompletionItems behavior
private static final Set<Integer> PREFERRED_RULES = Set.of(
DRL10Parser.RULE_drlIdentifier,
DRL10Parser.RULE_drlQualifiedName,
DRL10Parser.RULE_stringId,
DRL10Parser.RULE_consequenceBody
);

private DRLCompletionHelper() {
}

Expand All @@ -47,11 +60,15 @@ public static List<CompletionItem> getCompletionItems(String text, Position care
}

static List<CompletionItem> getCompletionItems(DRL10Parser drlParser, int nodeIndex) {
CodeCompletionCore core = new CodeCompletionCore(drlParser, null, null);
CodeCompletionCore core = new CodeCompletionCore(drlParser, PREFERRED_RULES, Tokens.IGNORED);
CodeCompletionCore.CandidatesCollection candidates = core.collectCandidates(nodeIndex, null);

if (candidates.rules.containsKey(DRL10Parser.RULE_consequenceBody)) {
// in RHS consequence, parser cannot suggest DRL_RHS_END because of island mode approach, so we add it manually
candidates.tokens.put(DRL10Lexer.DRL_RHS_END, List.of());
}

return candidates.tokens.keySet().stream().filter(Objects::nonNull)
.filter(integer -> !Tokens.IGNORED.contains(integer))
.map(integer -> drlParser.getVocabulary().getDisplayName(integer).replace("'", ""))
.map(String::toLowerCase)
.map(k -> createCompletionItem(k, CompletionItemKind.Keyword))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class Tokens {
DRL10Lexer.MOD_ASSIGN, DRL10Lexer.LSHIFT_ASSIGN, DRL10Lexer.RSHIFT_ASSIGN, DRL10Lexer.URSHIFT_ASSIGN,
DRL10Lexer.ARROW, DRL10Lexer.COLONCOLON, DRL10Lexer.AT, DRL10Lexer.ELLIPSIS, DRL10Lexer.WS, DRL10Lexer.COMMENT,
DRL10Lexer.LINE_COMMENT, DRL10Lexer.IDENTIFIER, DRL10Lexer.TEXT,
DRL10Lexer.DRL_STRING_LITERAL,

DRL10Lexer.RHS_COMMENT, DRL10Lexer.RHS_LINE_COMMENT, DRL10Lexer.RHS_STRING_LITERAL, DRL10Lexer.RHS_NAMED_CONSEQUENCE_THEN, DRL10Lexer.RHS_CHUNK
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,124 @@
import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.services.LanguageClient;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class DRLCompletionHelperTest {

@Disabled("TBD")
@Test
void getCompletionItems() {
// TODO
String text = "rule MyRule when Dog(name == \"Bart\") then end";
CompletionParams completionParams = new CompletionParams();
completionParams.setTextDocument(new TextDocumentIdentifier("myDocument"));
String text = """
package org.example;

import org.example.model.Dog;

global java.util.List list;

rule MyRule
when
$dog : Dog(name == "Bart")
then
list.add($dog.getName());
end
""";

Position caretPosition = new Position();
List<CompletionItem> result;

// at the beginning of the file
caretPosition.setCharacter(0); // caret character(column) position is zero-based
caretPosition.setLine(0); // caret line position is zero-based
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("package", "unit"); // once in a file
assertThat(completionItemStrings(result)).contains("import", "global", "rule"); // top level statements

// 'package '
caretPosition.setCharacter(8);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(result).isEmpty(); // no completion for identifier

// 'package org.example;\n\n'
caretPosition.setLine(2);
caretPosition.setCharacter(0);
caretPosition.setLine(-1); // -1 needed because of int row = caretPosition == null ? -1 : caretPosition
// .getLine()+1; // caret line position is zero based
completionParams.setPosition(caretPosition);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).doesNotContain("package"); // once in a file (already used)
assertThat(completionItemStrings(result)).contains("unit"); // once in a file (not yet used)
assertThat(completionItemStrings(result)).contains("import", "global", "rule"); // top level statements

// 'import '
caretPosition.setLine(2);
caretPosition.setCharacter(7);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("static", "function", "accumulate", "acc"); // 'import static' etc.

// 'import org.example.model.Dog;\n\n'
caretPosition.setLine(4);
caretPosition.setCharacter(0);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).doesNotContain("package", "unit"); // should be placed before import
assertThat(completionItemStrings(result)).contains("import", "global", "rule"); // top level statements

// 'global '
caretPosition.setLine(4);
caretPosition.setCharacter(7);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("boolean", "int"); // types (primitives are not useful, but valid)

// `rule My`
caretPosition.setLine(6);
caretPosition.setCharacter(7);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(result).isEmpty(); // no completion for identifier

// ` wh`
caretPosition.setLine(7);
caretPosition.setCharacter(3);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("when", "salience"); // inside rule

// ` $d`
caretPosition.setLine(8);
caretPosition.setCharacter(6);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("exists", "not"); // inside LHS

// ` $dog : Dog(`
caretPosition.setLine(8);
caretPosition.setCharacter(15);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(result).isEmpty(); // no completion for identifier

// ` th`
caretPosition.setLine(9);
caretPosition.setCharacter(3);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).doesNotContain("salience"); // attribute is not allowed
assertThat(completionItemStrings(result)).contains("then", "exists"); // inside LHS

// ` li`
caretPosition.setLine(10);
caretPosition.setCharacter(6);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("end"); // inside RHS

// at the end of the file
caretPosition.setLine(12);
caretPosition.setCharacter(0);
result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
assertThat(completionItemStrings(result)).contains("import", "global", "rule"); // top level statements
}

List<CompletionItem> result = DRLCompletionHelper.getCompletionItems(text, caretPosition, getLanguageClient());
CompletionItem completionItem = result.get(0);
assertThat(completionItem.getInsertText()).isEqualTo("suggestion");
private List<String> completionItemStrings(List<CompletionItem> result) {
return result.stream().map(CompletionItem::getInsertText).toList();
}

private LanguageClient getLanguageClient() {
Expand Down
8 changes: 8 additions & 0 deletions drools-completion/src/test/resources/logging.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
handlers = java.util.logging.ConsoleHandler

java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# To debug CodeCompletionCore
#com.vmware.antlr4c3.CodeCompletionCore.level = FINE