Skip to content

Commit

Permalink
Provide documentation on hover, in .kafka files
Browse files Browse the repository at this point in the history
Fixes jlandersen#149

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed May 2, 2021
1 parent 399d860 commit 356e190
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to `Tools for Apache Kafka®` are documented in this file.
- Hide internal [strimzi](https://strimzi.io/) topics/consumers by default. See [#176](https://github.com/jlandersen/vscode-kafka/pull/176).
- Validation for available topics in `.kafka` files. See [#153](https://github.com/jlandersen/vscode-kafka/issues/153).
- Simplify snippets. See [#180](https://github.com/jlandersen/vscode-kafka/pull/180).
- Hover support in `.kafka` files. See [#149](https://github.com/jlandersen/vscode-kafka/issues/149).

## [0.12.0] - 2021-04-26
### Added
Expand Down
18 changes: 17 additions & 1 deletion src/kafka-file/kafkaFileClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ export function startLanguageClient(
const diagnostics = new KafkaFileDiagnostics(kafkaFileDocuments, languageService, clusterSettings, clientAccessor, modelProvider, workspaceSettings);
context.subscriptions.push(diagnostics);

// Hover
const hover = new KafkaFileHoverProvider(kafkaFileDocuments, languageService);
context.subscriptions.push(
vscode.languages.registerHoverProvider(documentSelector, hover)
);

// Open / Close document
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(e => {
if (e.languageId === 'kafka') {
Expand Down Expand Up @@ -277,7 +283,7 @@ class KafkaFileCompletionItemProvider extends AbstractKafkaFileFeature implement
return runSafeAsync(async () => {
const kafkaFileDocument = this.getKafkaFileDocument(document);
return this.languageService.doComplete(document, kafkaFileDocument, this.workspaceSettings.producerFakerJSEnabled, position);
}, new vscode.CompletionList(), `Error while computing code lenses for ${document.uri}`, token);
}, new vscode.CompletionList(), `Error while computing completion for ${document.uri}`, token);
}

}
Expand Down Expand Up @@ -359,3 +365,13 @@ class KafkaFileDiagnostics extends AbstractKafkaFileFeature implements vscode.Di
this.diagnosticCollection.dispose();
}
}

class KafkaFileHoverProvider extends AbstractKafkaFileFeature implements vscode.HoverProvider {
provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.Hover> {
return runSafeAsync(async () => {
const kafkaFileDocument = this.getKafkaFileDocument(document);
return this.languageService.doHover(document, kafkaFileDocument, position);
}, null, `Error while computing hover for ${document.uri}`, token);
}

}
23 changes: 21 additions & 2 deletions src/kafka-file/languageservice/kafkaFileLanguageService.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { CodeLens, CompletionList, Diagnostic, Position, TextDocument, Uri } from "vscode";
import { CodeLens, CompletionList, Diagnostic, Hover, Position, TextDocument, Uri } from "vscode";
import { ClientState, ConsumerLaunchState } from "../../client";
import { BrokerConfigs } from "../../client/config";
import { ProducerLaunchState } from "../../client/producer";
import { KafkaFileDocument, parseKafkaFile } from "./parser/kafkaFileParser";
import { KafkaFileCodeLenses } from "./services/codeLensProvider";
import { KafkaFileCompletion } from "./services/completion";
import { KafkaFileDiagnostics } from "./services/diagnostics";
import { KafkaFileHover } from "./services/hover";

/**
* Provider API which gets the state for a given producer.
Expand Down Expand Up @@ -49,6 +50,7 @@ export interface TopicProvider {
*
*/
export interface LanguageService {

/**
* Parse the given text document and returns an AST.
*
Expand Down Expand Up @@ -85,6 +87,15 @@ export interface LanguageService {
* @param kafkaFileDocument the parsed AST.
*/
doDiagnostics(document: TextDocument, kafkaFileDocument: KafkaFileDocument, producerFakerJSEnabled: boolean): Promise<Diagnostic[]>;

/**
* Returns the hover result for the given text document and parsed AST at given position.
*
* @param document the text document.
* @param kafkaFileDocument the parsed AST.
* @param position the position where the hover was triggered.
*/
doHover(document: TextDocument, kafkaFileDocument: KafkaFileDocument, position: Position): Promise<Hover | undefined>;
}

/**
Expand All @@ -100,10 +111,18 @@ export function getLanguageService(producerLaunchStateProvider: ProducerLaunchSt
const codeLenses = new KafkaFileCodeLenses(producerLaunchStateProvider, consumerLaunchStateProvider, selectedClusterProvider);
const completion = new KafkaFileCompletion(selectedClusterProvider, topicProvider);
const diagnostics = new KafkaFileDiagnostics(selectedClusterProvider, topicProvider);
const hover = new KafkaFileHover(selectedClusterProvider, topicProvider);
return {
parseKafkaFileDocument: (document: TextDocument) => parseKafkaFile(document),
getCodeLenses: codeLenses.getCodeLenses.bind(codeLenses),
doComplete: completion.doComplete.bind(completion),
doDiagnostics: diagnostics.doDiagnostics.bind(diagnostics)
doDiagnostics: diagnostics.doDiagnostics.bind(diagnostics),
doHover: hover.doHover.bind(hover)
};
}

export function createTopicDocumentation(topic: TopicDetail): string {
return `Topic \`${topic.id}\`\n` +
` * partition count: \`${topic.partitionCount}\`\n` +
` * replication factor: \`${topic.replicationFactor}\`\n`;
}
39 changes: 32 additions & 7 deletions src/kafka-file/languageservice/parser/kafkaFileParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export enum NodeKind {
export interface Node {
start: Position;
end: Position;
range(): Range;
findNodeBefore(offset: Position): Node;
findNodeAt(offset: Position): Node;
lastChild: Node | undefined;
parent: Node | undefined;
kind: NodeKind;
Expand All @@ -32,10 +34,20 @@ class BaseNode implements Node {

}

public range(): Range {
const start = this.start;
const end = this.end;
return new Range(start, end);
}

public findNodeBefore(offset: Position): Node {
return this;
}

public findNodeAt(offset: Position): Node {
return this;
}

public get lastChild(): Node | undefined { return undefined; }
}

Expand Down Expand Up @@ -65,6 +77,17 @@ class ChildrenNode<T extends Node> extends BaseNode {
return this;
}

public findNodeAt(offset: Position): Node {
const idx = findFirst(this.children, c => offset.isBeforeOrEqual(c.start)) - 1;
if (idx >= 0) {
const child = this.children[idx];
if (offset.isAfter(child.start) && offset.isBeforeOrEqual(child.end)) {
return child.findNodeAt(offset);
}
}
return this;
}

public get lastChild(): Node | undefined { return this.children.length ? this.children[this.children.length - 1] : void 0; };
}

Expand All @@ -84,6 +107,7 @@ export class Chunk extends BaseNode {
constructor(public readonly content: string, start: Position, end: Position, kind: NodeKind) {
super(start, end, kind);
}

}

export class Property extends BaseNode {
Expand All @@ -106,12 +130,6 @@ export class Property extends BaseNode {
return this.value?.content.trim();
}

public get propertyRange(): Range {
const start = this.start;
const end = this.end;
return new Range(start, end);
}

public get propertyKeyRange(): Range {
const start = this.start;
const end = this.assignerCharacter ? new Position(this.start.line, this.assignerCharacter) : this.end;
Expand Down Expand Up @@ -159,6 +177,13 @@ export class Property extends BaseNode {
}
return true;
}

findNodeAt(position : Position) : Node {
if (this.isBeforeAssigner(position)) {
return this.key?.findNodeAt(position) || this;
}
return this.value?.findNodeAt(position) || this;
}
}

export abstract class Block extends ChildrenNode<Property | Chunk> {
Expand All @@ -175,7 +200,7 @@ export abstract class Block extends ChildrenNode<Property | Chunk> {

getPropertyValue(name: string): string | undefined {
const property = this.getProperty(name);
return property?.value?.content;
return property?.propertyValue;
}

getProperty(name: string): Property | undefined {
Expand Down
9 changes: 2 additions & 7 deletions src/kafka-file/languageservice/services/completion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TextDocument, Position, CompletionList, CompletionItem, SnippetString, MarkdownString, CompletionItemKind, Range } from "vscode";
import { SelectedClusterProvider, TopicDetail, TopicProvider } from "../kafkaFileLanguageService";
import { createTopicDocumentation, SelectedClusterProvider, TopicProvider } from "../kafkaFileLanguageService";
import { consumerModel, fakerjsAPIModel, Model, ModelDefinition, producerModel } from "../model";
import { Block, BlockType, Chunk, ConsumerBlock, KafkaFileDocument, MustacheExpression, NodeKind, ProducerBlock, Property } from "../parser/kafkaFileParser";

Expand Down Expand Up @@ -235,19 +235,14 @@ export class KafkaFileCompletion {
return;
}

function createDocumentation(topic: TopicDetail): string {
return `Topic \`${topic.id}\`\n` +
` * partition count: \`${topic.partitionCount}\`\n` +
` * replication factor: \`${topic.replicationFactor}\`\n`;
}
const valueRange = property.propertyValueRange;
try {
const topics = await this.topicProvider.getTopics(clusterId);
topics.forEach((topic) => {
const value = topic.id;
const item = new CompletionItem(value);
item.kind = CompletionItemKind.Value;
item.documentation = new MarkdownString(createDocumentation(topic));
item.documentation = new MarkdownString(createTopicDocumentation(topic));
const insertText = new SnippetString(' ');
insertText.appendText(value);
item.insertText = insertText;
Expand Down
6 changes: 3 additions & 3 deletions src/kafka-file/languageservice/services/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,14 @@ export class KafkaFileDiagnostics {
const assigner = property.assignerCharacter;
if (!assigner) {
// Error => topic
const range = property.propertyRange;
const range = property.range();
diagnostics.push(new Diagnostic(range, `Missing ':' sign after '${propertyName}'`, DiagnosticSeverity.Error));
return;
}
// 1.2. property must declare a key
if (!propertyName) {
// Error => :string
const range = property.propertyRange;
const range = property.range();
diagnostics.push(new Diagnostic(range, "Property must define a name before ':' sign", DiagnosticSeverity.Error));
return;
}
Expand Down Expand Up @@ -280,7 +280,7 @@ export class KafkaFileDiagnostics {
// The topic validation is done, only when the cluster is connected
if (!await this.topicProvider.getTopic(clusterId, topicId)) {
// The topic doesn't exist, report an error
const range = topicProperty.propertyTrimmedValueRange || topicProperty.propertyRange;
const range = topicProperty.propertyTrimmedValueRange || topicProperty.range();
const autoCreate = await this.topicProvider.getAutoCreateTopicEnabled(clusterId);
const errorMessage = getTopicErrorMessage(topicId, autoCreate, blockType);
const severity = getTopicErrorSeverity(autoCreate);
Expand Down
Loading

0 comments on commit 356e190

Please sign in to comment.