Skip to content

Commit de2cccc

Browse files
authored
feat: hover support for proeprty binding (#645)
* feat: hover support for proeprty binding * fix: add seprator in doc and remove visibility * fix: tooltip title
1 parent 746c257 commit de2cccc

File tree

12 files changed

+476
-98
lines changed

12 files changed

+476
-98
lines changed

.changeset/stupid-zoos-mate.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch
3+
"vscode-ui5-language-assistant": patch
4+
"@ui5-language-assistant/language-server": patch
5+
"@ui5-language-assistant/binding": patch
6+
---
7+
8+
add hover support for proeprty binding

packages/binding/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BINDING_ISSUE_TYPE } from "./types";
33

44
export { getCompletionItems } from "./services/completion";
55
export { bindingValidators } from "./services/diagnostics";
6+
export { getHover } from "./services/hover";
67
export type { BindingIssue } from "./types";
78

89
export function isBindingIssue<T extends { issueType: string }>(

packages/binding/src/definition/definition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "../types";
1414
import { ui5NodeToFQN } from "@ui5-language-assistant/logic-utils";
1515
import { forOwn } from "lodash";
16-
import { getDocumentation } from "../services/completion/providers/documentation";
16+
import { getDocumentation } from "../utils";
1717
import { fallBackElements } from "./fall-back-definition";
1818

1919
const isBindingInfoName = (name: string): name is BindingInfoName => {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { XMLAttribute } from "@xml-tools/ast";
2+
import { getUI5PropertyByXMLAttributeKey } from "@ui5-language-assistant/logic-utils";
3+
import { getLogger } from "../../utils";
4+
import { Hover } from "vscode-languageserver";
5+
import {
6+
parseBinding,
7+
isBindingAllowed,
8+
isBindingExpression,
9+
extractBindingSyntax,
10+
rangeContained,
11+
BindingParserTypes,
12+
} from "@ui5-language-assistant/binding-parser";
13+
import { Position } from "vscode-languageserver-types";
14+
import { BindContext } from "../../types";
15+
import { PROPERTY_BINDING_INFO } from "../../constant";
16+
import { getDocumentation } from "../../utils";
17+
import { UI5Typedef } from "@ui5-language-assistant/semantic-model-types";
18+
19+
const getHoverFromBinding = (
20+
context: BindContext,
21+
propertyBinding: UI5Typedef,
22+
binding: BindingParserTypes.StructureValue
23+
): Hover | undefined => {
24+
let hover: Hover | undefined;
25+
const cursorPos = context.textDocumentPosition?.position;
26+
for (const element of binding.elements) {
27+
if (
28+
cursorPos &&
29+
element.range &&
30+
rangeContained(element.range, {
31+
start: cursorPos,
32+
end: cursorPos,
33+
})
34+
) {
35+
// check if cursor is on key range
36+
if (
37+
cursorPos &&
38+
element.key &&
39+
rangeContained(element.key.range, {
40+
start: cursorPos,
41+
end: cursorPos,
42+
})
43+
) {
44+
// check valid key
45+
const property = propertyBinding.properties.find(
46+
(prop) => prop.name === element.key?.originalText
47+
);
48+
if (property) {
49+
return { contents: getDocumentation(context, property, true) };
50+
}
51+
}
52+
53+
// check collection value as they may have property binding
54+
if (element.value?.type === "collection-value") {
55+
for (const collectionEl of element.value.elements) {
56+
if (collectionEl.type !== "structure-value") {
57+
continue;
58+
}
59+
hover = getHoverFromBinding(context, propertyBinding, collectionEl);
60+
if (hover) {
61+
return hover;
62+
}
63+
}
64+
}
65+
}
66+
}
67+
return hover;
68+
};
69+
70+
export const getHover = (
71+
context: BindContext,
72+
attribute: XMLAttribute
73+
): Hover | undefined => {
74+
try {
75+
const propBinding = context.ui5Model.typedefs[PROPERTY_BINDING_INFO];
76+
if (!propBinding) {
77+
return;
78+
}
79+
const ui5Property = getUI5PropertyByXMLAttributeKey(
80+
attribute,
81+
context.ui5Model
82+
);
83+
if (!ui5Property) {
84+
return;
85+
}
86+
const value = attribute.syntax.value;
87+
const text = attribute.value ?? "";
88+
if (text.trim().length === 0) {
89+
return;
90+
}
91+
92+
const extractedText = extractBindingSyntax(text);
93+
for (const bindingSyntax of extractedText) {
94+
const { expression, startIndex } = bindingSyntax;
95+
if (isBindingExpression(expression)) {
96+
continue;
97+
}
98+
/* istanbul ignore next */
99+
const position: Position = {
100+
character: (value?.startColumn ?? 0) + startIndex,
101+
line: value?.startLine ? value.startLine - 1 : 0, // zero based index
102+
};
103+
const { ast, errors } = parseBinding(expression, position);
104+
const cursorPos = context.textDocumentPosition?.position;
105+
const binding = ast.bindings.find(
106+
(b) =>
107+
cursorPos &&
108+
b.range &&
109+
rangeContained(b.range, { start: cursorPos, end: cursorPos })
110+
);
111+
if (!binding) {
112+
continue;
113+
}
114+
if (!isBindingAllowed(text, binding, errors)) {
115+
continue;
116+
}
117+
return getHoverFromBinding(context, propBinding, binding);
118+
}
119+
return;
120+
} catch (error) {
121+
getLogger().debug("validatePropertyBindingInfo failed:", error);
122+
return;
123+
}
124+
};

packages/binding/src/services/completion/providers/documentation.ts renamed to packages/binding/src/utils/documentation.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {
22
UI5Type,
33
UI5TypedefProp,
44
} from "@ui5-language-assistant/semantic-model-types";
5-
import { BindContext } from "../../../types";
5+
import { BindContext } from "../types";
66
import { MarkupKind } from "vscode-languageserver-types";
77
import { ui5NodeToFQN, getLink } from "@ui5-language-assistant/logic-utils";
8-
import { PROPERTY_BINDING_INFO } from "../../../constant";
8+
import { PROPERTY_BINDING_INFO } from "../constant";
99

1010
const getType = (type: UI5Type | undefined): string[] => {
1111
const result: string[] = [];
@@ -37,17 +37,18 @@ const getType = (type: UI5Type | undefined): string[] => {
3737

3838
export const getDocumentation = (
3939
context: BindContext,
40-
prop: UI5TypedefProp
40+
prop: UI5TypedefProp,
41+
forHover = false
4142
): {
4243
kind: MarkupKind;
4344
value: string;
4445
} => {
4546
const link = getLink(context.ui5Model, PROPERTY_BINDING_INFO);
4647
const values: string[] = [
47-
`\`typedef ${PROPERTY_BINDING_INFO}\``,
48+
`\`(typedef) ${PROPERTY_BINDING_INFO}\``,
49+
forHover ? `---` : "",
4850
`**Type:** ${getType(prop.type)}`,
4951
`**Description:** ${prop.description}`,
50-
`**Visibility:** ${prop.visibility}`,
5152
`**Optional:** ${prop.optional}`,
5253
`[More information](${link})`,
5354
];

packages/binding/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export {
1010
export { getCursorContext } from "./cursor";
1111

1212
export { getLogger } from "./logger";
13+
14+
export { getDocumentation } from "./documentation";

0 commit comments

Comments
 (0)