diff --git a/client/src/services/context.ts b/client/src/services/context.ts index 0872d57..932e7a5 100644 --- a/client/src/services/context.ts +++ b/client/src/services/context.ts @@ -5,21 +5,23 @@ import { getOriginalVariableName } from "../utils/utils"; export function handleContext(context: LJContext) { extension.context = context; - updateContextForSelection(extension.currentSelection); + if (!extension.file || !extension.currentSelection) return; + + // update variables based on new context in current selection + const { allVars, visibleVars } = getSelectionContextVariables(extension.file, extension.currentSelection); + extension.context.visibleVars = visibleVars; + extension.context.allVars = allVars; extension.webview.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor }); } -export function updateContextForSelection(selection: Range) { - if (!selection) return; - - const globalVars = extension.context.globalVars || []; - const localVars = extension.context.localVars || []; - const variablesInScope = getVariablesInScope(localVars, extension.file, selection); - const visibleVarsByPosition = getVisibleVariables(variablesInScope, extension.file, selection, false); - const visibleVarsByAnnotationPosition = getVisibleVariables(variablesInScope, extension.file, selection, true); +export function getSelectionContextVariables(file: string, selection: Range): { visibleVars: LJVariable[]; allVars: LJVariable[] } { + const globalVars = extension.context?.globalVars || []; + const localVars = extension.context?.localVars || []; + const variablesInScope = getVariablesInScope(localVars, file, selection); + const visibleVarsByPosition = getVisibleVariables(variablesInScope, file, selection, false); + const visibleVarsByAnnotationPosition = getVisibleVariables(variablesInScope, file, selection, true); const allVars = sortVariables(normalizeVariableRefinements([...globalVars, ...visibleVarsByPosition])); - extension.context.visibleVars = visibleVarsByAnnotationPosition; - extension.context.allVars = allVars; + return { visibleVars: visibleVarsByAnnotationPosition, allVars }; } function getVariablesInScope(variables: LJVariable[], file: string, selection: Range): LJVariable[] { diff --git a/client/src/services/events.ts b/client/src/services/events.ts index 7306327..e7edd91 100644 --- a/client/src/services/events.ts +++ b/client/src/services/events.ts @@ -2,10 +2,10 @@ import * as vscode from 'vscode'; import { extension } from '../state'; import { updateStateMachine } from './state-machine'; import { SELECTION_DEBOUNCE_MS } from '../utils/constants'; -import { normalizeRange, updateContextForSelection } from './context'; -import { normalizeFilePath } from '../utils/utils'; -import { Range } from '../types/context'; +import { getSelectionContextVariables, normalizeRange } from './context'; +import { normalizeFilePath, toRange } from '../utils/utils'; import { updateErrorAtCursor } from './diagnostics'; +import type { Range } from '../types/context'; let selectionTimeout: NodeJS.Timeout | null = null; @@ -61,15 +61,12 @@ export async function onSelectionChange(event: vscode.TextEditorSelectionChangeE */ function handleContextUpdate(selection: vscode.Selection) { if (!extension.file || !extension.context) return; - const range: Range = { - lineStart: selection.start.line, - colStart: selection.start.character, - lineEnd: selection.end.line, - colEnd: selection.end.character - }; - const normalizedRange = normalizeRange(range); + + const normalizedRange = normalizeRange(toRange(selection)); + const { allVars, visibleVars } = getSelectionContextVariables(extension.file, normalizedRange); extension.currentSelection = normalizedRange; - updateContextForSelection(normalizedRange); + extension.context.visibleVars = visibleVars; + extension.context.allVars = allVars; updateErrorAtCursor(); extension.webview?.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor }); } \ No newline at end of file diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index bf9938f..7b460eb 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -1,5 +1,8 @@ import * as vscode from 'vscode'; import { extension } from '../state'; +import type { Range, LJVariable } from '../types/context'; +import { getSelectionContextVariables } from './context'; +import { getOriginalVariableName, normalizeFilePath, toRange } from '../utils/utils'; /** * Initializes hover provider for LiquidJava diagnostics @@ -7,19 +10,41 @@ import { extension } from '../state'; export function registerHover() { vscode.languages.registerHoverProvider('java', { provideHover(document, position) { - // if webview is visible, do not show hover - if (extension.webview?.isVisible()) return null; - - // get lj diagnostic at the current position - const diagnostics = vscode.languages.getDiagnostics(document.uri); - const diagnostic = diagnostics.find(d => d.range.contains(position) && d.source === 'liquidjava'); - if (!diagnostic) return null; - - // create hover content with link to open webview const hoverContent = new vscode.MarkdownString(); hoverContent.isTrusted = true; - hoverContent.appendMarkdown(`\n\n[Open LiquidJava view](command:liquidjava.showView) for more details.`); + + const variable = getHoveredVariable(document, position); + if (variable && variable.mainRefinement && variable.mainRefinement !== 'true') + hoverContent.appendCodeblock(`@Refinement("${variable.mainRefinement}")`, 'java'); + + const diagnostics = vscode.languages.getDiagnostics(document.uri); + const containsDiagnostic = !!diagnostics.find(d => d.range.contains(position) && d.source === 'liquidjava'); + if (containsDiagnostic) { + if (hoverContent.value.length > 0) hoverContent.appendMarkdown(`\n\n`); + hoverContent.appendMarkdown(`[Open LiquidJava view](command:liquidjava.showView) for more details.`); + } + if (hoverContent.value.length === 0) return null; return new vscode.Hover(hoverContent); } }); } + +function getHoveredVariable(document: vscode.TextDocument, position: vscode.Position): LJVariable | null { + if (!extension.context) return null; + + const wordRange = document.getWordRangeAtPosition(position, /[#]?[A-Za-z_][A-Za-z0-9_#]*/); + if (!wordRange) return null; + + const hoveredWord = document.getText(wordRange); + const file = normalizeFilePath(document.uri.fsPath); + + // we need to use single point cursor position after variable to get all variables until that point + const positionAfterVariable = { + lineStart: wordRange.end.line, + colStart: wordRange.end.character, + lineEnd: wordRange.end.line, + colEnd: wordRange.end.character + }; + const { allVars } = getSelectionContextVariables(file, positionAfterVariable); + return allVars.find(variable => getOriginalVariableName(variable.name) === hoveredWord); +} diff --git a/client/src/utils/utils.ts b/client/src/utils/utils.ts index 14b233b..093c8be 100644 --- a/client/src/utils/utils.ts +++ b/client/src/utils/utils.ts @@ -2,7 +2,9 @@ import * as fs from "fs"; import * as path from "path"; import * as net from "net"; import * as child_process from "child_process"; +import * as vscode from "vscode"; import { JAVA_BINARY } from "./constants"; +import { Range } from "../types/context"; /** * Finds the Java executable in the system, either in JAVA_HOME or in PATH @@ -130,10 +132,22 @@ export function getSimpleName(qualifiedName: string): string { } export function getOriginalVariableName(name: string): string { - return name.split("_")[0].replace(/^#/, ''); + if (name.startsWith("this#")) return name.replace(/^this#/, ''); + if (name.startsWith("#")) return name.split("_")[0].replace(/^#/, ''); + return name; + } export function normalizeFilePath(fsPath: string): string { // uppercase windows drive letter (c:\ -> C:\) return path.normalize(fsPath).replace(/^([a-z]):\\/, (_, drive) => drive.toUpperCase() + ':\\'); +} + +export function toRange(selection: vscode.Selection | vscode.Range): Range { + return { + lineStart: selection.start.line, + colStart: selection.start.character, + lineEnd: selection.end.line, + colEnd: selection.end.character + }; } \ No newline at end of file