Skip to content

Commit c5de9ba

Browse files
authored
Provide hover documentation on keywords (#1842)
1 parent b739dce commit c5de9ba

File tree

2 files changed

+91
-9
lines changed

2 files changed

+91
-9
lines changed

packages/langium/src/lsp/hover-provider.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import type { AstNode } from '../syntax-tree.js';
1313
import type { MaybePromise } from '../utils/promise-utils.js';
1414
import type { LangiumDocument } from '../workspace/documents.js';
1515
import type { DocumentationProvider } from '../documentation/documentation-provider.js';
16-
import { findDeclarationNodeAtOffset } from '../utils/cst-utils.js';
16+
import { findCommentNode, findDeclarationNodeAtOffset } from '../utils/cst-utils.js';
17+
import { isKeyword } from '../languages/generated/ast.js';
18+
import { isJSDoc, parseJSDoc } from '../documentation/jsdoc.js';
19+
import { isAstNodeWithComment } from '../serializer/json-serializer.js';
1720

1821
/**
1922
* Language-specific service for handling hover requests.
@@ -48,13 +51,36 @@ export abstract class AstNodeHoverProvider implements HoverProvider {
4851
if (targetNode) {
4952
return this.getAstNodeHoverContent(targetNode);
5053
}
54+
55+
// Add support for documentation on keywords
56+
if (isKeyword(cstNode.grammarSource)) {
57+
return this.getKeywordHoverContent(cstNode.grammarSource);
58+
}
5159
}
5260
}
5361
return undefined;
5462
}
5563

5664
protected abstract getAstNodeHoverContent(node: AstNode): MaybePromise<Hover | undefined>;
5765

66+
protected getKeywordHoverContent(node: AstNode): MaybePromise<Hover | undefined> {
67+
let comment = isAstNodeWithComment(node) ? node.$comment : undefined;
68+
if (!comment) {
69+
comment = findCommentNode(node.$cstNode, ['ML_COMMENT'])?.text;
70+
}
71+
if (comment && isJSDoc(comment)) {
72+
const content = parseJSDoc(comment).toMarkdown();
73+
if (content) {
74+
return {
75+
contents: {
76+
kind: 'markdown',
77+
value: content
78+
}
79+
};
80+
}
81+
}
82+
return undefined;
83+
}
5884
}
5985

6086
export class MultilineCommentHoverProvider extends AstNodeHoverProvider {

packages/langium/test/lsp/hover.test.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
* terms of the MIT License, which is available in the project root.
55
******************************************************************************/
66

7-
import { describe, test } from 'vitest';
7+
import { beforeAll, describe, test } from 'vitest';
8+
import type { AsyncDisposable} from 'langium';
89
import { EmptyFileSystem } from 'langium';
9-
import { createLangiumGrammarServices } from 'langium/grammar';
10+
import { createLangiumGrammarServices, createServicesForGrammar } from 'langium/grammar';
11+
import type { ExpectedHover} from 'langium/test';
1012
import { expectHover } from 'langium/test';
1113

12-
const text = `
14+
describe('Hover', () => {
15+
const text = `
1316
/**
1417
* I am a grammar file comment
1518
*/
@@ -27,12 +30,10 @@ const text = `
2730
* Hi I reference Rule {@linkcode X}
2831
*/
2932
<|>Y: value=<|>X;
30-
`;
31-
32-
const grammarServices = createLangiumGrammarServices(EmptyFileSystem).grammar;
33-
const hover = expectHover(grammarServices);
33+
`;
3434

35-
describe('Hover', () => {
35+
const grammarServices = createLangiumGrammarServices(EmptyFileSystem).grammar;
36+
const hover = expectHover(grammarServices);
3637

3738
test('Hovering over whitespace should not provide a hover', async () => {
3839
await hover({
@@ -74,3 +75,58 @@ describe('Hover', () => {
7475
});
7576
});
7677
});
78+
79+
describe('Hover on keywords', () => {
80+
81+
const grammar = `grammar HoverOnKeywords
82+
83+
entry Model:
84+
/** root keyword */ 'root' name=ID
85+
elements+=Tag*;
86+
87+
Tag: /** opening tag */ 'tag' name=ID /** closing tag */ 'tag';
88+
89+
hidden terminal WS: /\\s+/;
90+
terminal ID: /[_a-zA-Z][\\w_]*/;
91+
hidden terminal ML_COMMENT: /#\\*[\\s\\S]*?\\*#/;
92+
hidden terminal SL_COMMENT: /##[^\\n\\r]*/;
93+
`;
94+
95+
const text = `
96+
## SL_COMMENT
97+
<|>root name
98+
#* ML_COMMENT *#
99+
<|>tag first <|>tag
100+
`;
101+
102+
let hover: (expectedHover: ExpectedHover) => Promise<AsyncDisposable>;
103+
104+
beforeAll(async () => {
105+
const services = await createServicesForGrammar({
106+
grammar
107+
});
108+
hover = expectHover(services);
109+
});
110+
test('Hovering over root keyword', async () => {
111+
await hover({
112+
text,
113+
index: 0,
114+
hover: 'root keyword'
115+
});
116+
});
117+
test('Hovering over opening tag keyword', async () => {
118+
119+
await hover({
120+
text,
121+
index: 1,
122+
hover: 'opening tag'
123+
});
124+
});
125+
test('Hovering over closing tag keyword', async () => {
126+
await hover({
127+
text,
128+
index: 2,
129+
hover: 'closing tag'
130+
});
131+
});
132+
});

0 commit comments

Comments
 (0)