Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions client/src/services/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand Down
19 changes: 8 additions & 11 deletions client/src/services/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 });
}
45 changes: 35 additions & 10 deletions client/src/services/hover.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
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
*/
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);
}
16 changes: 15 additions & 1 deletion client/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
};
}