Skip to content

Commit 5eaec42

Browse files
authored
Variable Refinement Hover (#70)
1 parent 1cb07f5 commit 5eaec42

4 files changed

Lines changed: 71 additions & 33 deletions

File tree

client/src/services/context.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@ import { getOriginalVariableName } from "../utils/utils";
55

66
export function handleContext(context: LJContext) {
77
extension.context = context;
8-
updateContextForSelection(extension.currentSelection);
8+
if (!extension.file || !extension.currentSelection) return;
9+
10+
// update variables based on new context in current selection
11+
const { allVars, visibleVars } = getSelectionContextVariables(extension.file, extension.currentSelection);
12+
extension.context.visibleVars = visibleVars;
13+
extension.context.allVars = allVars;
914
extension.webview.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor });
1015
}
1116

12-
export function updateContextForSelection(selection: Range) {
13-
if (!selection) return;
14-
15-
const globalVars = extension.context.globalVars || [];
16-
const localVars = extension.context.localVars || [];
17-
const variablesInScope = getVariablesInScope(localVars, extension.file, selection);
18-
const visibleVarsByPosition = getVisibleVariables(variablesInScope, extension.file, selection, false);
19-
const visibleVarsByAnnotationPosition = getVisibleVariables(variablesInScope, extension.file, selection, true);
17+
export function getSelectionContextVariables(file: string, selection: Range): { visibleVars: LJVariable[]; allVars: LJVariable[] } {
18+
const globalVars = extension.context?.globalVars || [];
19+
const localVars = extension.context?.localVars || [];
20+
const variablesInScope = getVariablesInScope(localVars, file, selection);
21+
const visibleVarsByPosition = getVisibleVariables(variablesInScope, file, selection, false);
22+
const visibleVarsByAnnotationPosition = getVisibleVariables(variablesInScope, file, selection, true);
2023
const allVars = sortVariables(normalizeVariableRefinements([...globalVars, ...visibleVarsByPosition]));
21-
extension.context.visibleVars = visibleVarsByAnnotationPosition;
22-
extension.context.allVars = allVars;
24+
return { visibleVars: visibleVarsByAnnotationPosition, allVars };
2325
}
2426

2527
function getVariablesInScope(variables: LJVariable[], file: string, selection: Range): LJVariable[] {

client/src/services/events.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import * as vscode from 'vscode';
22
import { extension } from '../state';
33
import { updateStateMachine } from './state-machine';
44
import { SELECTION_DEBOUNCE_MS } from '../utils/constants';
5-
import { normalizeRange, updateContextForSelection } from './context';
6-
import { normalizeFilePath } from '../utils/utils';
7-
import { Range } from '../types/context';
5+
import { getSelectionContextVariables, normalizeRange } from './context';
6+
import { normalizeFilePath, toRange } from '../utils/utils';
87
import { updateErrorAtCursor } from './diagnostics';
8+
import type { Range } from '../types/context';
99

1010
let selectionTimeout: NodeJS.Timeout | null = null;
1111

@@ -61,15 +61,12 @@ export async function onSelectionChange(event: vscode.TextEditorSelectionChangeE
6161
*/
6262
function handleContextUpdate(selection: vscode.Selection) {
6363
if (!extension.file || !extension.context) return;
64-
const range: Range = {
65-
lineStart: selection.start.line,
66-
colStart: selection.start.character,
67-
lineEnd: selection.end.line,
68-
colEnd: selection.end.character
69-
};
70-
const normalizedRange = normalizeRange(range);
64+
65+
const normalizedRange = normalizeRange(toRange(selection));
66+
const { allVars, visibleVars } = getSelectionContextVariables(extension.file, normalizedRange);
7167
extension.currentSelection = normalizedRange;
72-
updateContextForSelection(normalizedRange);
68+
extension.context.visibleVars = visibleVars;
69+
extension.context.allVars = allVars;
7370
updateErrorAtCursor();
7471
extension.webview?.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor });
7572
}

client/src/services/hover.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
11
import * as vscode from 'vscode';
22
import { extension } from '../state';
3+
import type { Range, LJVariable } from '../types/context';
4+
import { getSelectionContextVariables } from './context';
5+
import { getOriginalVariableName, normalizeFilePath, toRange } from '../utils/utils';
36

47
/**
58
* Initializes hover provider for LiquidJava diagnostics
69
*/
710
export function registerHover() {
811
vscode.languages.registerHoverProvider('java', {
912
provideHover(document, position) {
10-
// if webview is visible, do not show hover
11-
if (extension.webview?.isVisible()) return null;
12-
13-
// get lj diagnostic at the current position
14-
const diagnostics = vscode.languages.getDiagnostics(document.uri);
15-
const diagnostic = diagnostics.find(d => d.range.contains(position) && d.source === 'liquidjava');
16-
if (!diagnostic) return null;
17-
18-
// create hover content with link to open webview
1913
const hoverContent = new vscode.MarkdownString();
2014
hoverContent.isTrusted = true;
21-
hoverContent.appendMarkdown(`\n\n[Open LiquidJava view](command:liquidjava.showView) for more details.`);
15+
16+
const variable = getHoveredVariable(document, position);
17+
if (variable && variable.mainRefinement && variable.mainRefinement !== 'true')
18+
hoverContent.appendCodeblock(`@Refinement("${variable.mainRefinement}")`, 'java');
19+
20+
const diagnostics = vscode.languages.getDiagnostics(document.uri);
21+
const containsDiagnostic = !!diagnostics.find(d => d.range.contains(position) && d.source === 'liquidjava');
22+
if (containsDiagnostic) {
23+
if (hoverContent.value.length > 0) hoverContent.appendMarkdown(`\n\n`);
24+
hoverContent.appendMarkdown(`[Open LiquidJava view](command:liquidjava.showView) for more details.`);
25+
}
26+
if (hoverContent.value.length === 0) return null;
2227
return new vscode.Hover(hoverContent);
2328
}
2429
});
2530
}
31+
32+
function getHoveredVariable(document: vscode.TextDocument, position: vscode.Position): LJVariable | null {
33+
if (!extension.context) return null;
34+
35+
const wordRange = document.getWordRangeAtPosition(position, /[#]?[A-Za-z_][A-Za-z0-9_#]*/);
36+
if (!wordRange) return null;
37+
38+
const hoveredWord = document.getText(wordRange);
39+
const file = normalizeFilePath(document.uri.fsPath);
40+
41+
// we need to use single point cursor position after variable to get all variables until that point
42+
const positionAfterVariable = {
43+
lineStart: wordRange.end.line,
44+
colStart: wordRange.end.character,
45+
lineEnd: wordRange.end.line,
46+
colEnd: wordRange.end.character
47+
};
48+
const { allVars } = getSelectionContextVariables(file, positionAfterVariable);
49+
return allVars.find(variable => getOriginalVariableName(variable.name) === hoveredWord);
50+
}

client/src/utils/utils.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import * as fs from "fs";
22
import * as path from "path";
33
import * as net from "net";
44
import * as child_process from "child_process";
5+
import * as vscode from "vscode";
56
import { JAVA_BINARY } from "./constants";
7+
import { Range } from "../types/context";
68

79
/**
810
* Finds the Java executable in the system, either in JAVA_HOME or in PATH
@@ -130,10 +132,22 @@ export function getSimpleName(qualifiedName: string): string {
130132
}
131133

132134
export function getOriginalVariableName(name: string): string {
133-
return name.split("_")[0].replace(/^#/, '');
135+
if (name.startsWith("this#")) return name.replace(/^this#/, '');
136+
if (name.startsWith("#")) return name.split("_")[0].replace(/^#/, '');
137+
return name;
138+
134139
}
135140

136141
export function normalizeFilePath(fsPath: string): string {
137142
// uppercase windows drive letter (c:\ -> C:\)
138143
return path.normalize(fsPath).replace(/^([a-z]):\\/, (_, drive) => drive.toUpperCase() + ':\\');
144+
}
145+
146+
export function toRange(selection: vscode.Selection | vscode.Range): Range {
147+
return {
148+
lineStart: selection.start.line,
149+
colStart: selection.start.character,
150+
lineEnd: selection.end.line,
151+
colEnd: selection.end.character
152+
};
139153
}

0 commit comments

Comments
 (0)