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
12 changes: 10 additions & 2 deletions client/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { onActiveFileChange } from '../services/events';
import type { LJDiagnostic } from "../types/diagnostics";
import { LJContext } from '../types/context';
import { handleContext } from '../services/context';
import { isCursorInsideLiquidJavaAnnotation } from '../services/annotation';

/**
* Starts the client and connects it to the language server
Expand All @@ -29,6 +30,13 @@ export async function runClient(context: vscode.ExtensionContext, port: number)
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ language: "java" }],
middleware: {
didSave: async (document, next) => {
// skip verification if the cursor is inside a LiquidJava annotation
if (isCursorInsideLiquidJavaAnnotation(document)) return;
await next(document);
},
},
};
extension.client = new LanguageClient("liquidJavaServer", "LiquidJava Server", serverOptions, clientOptions);

Expand Down Expand Up @@ -62,8 +70,8 @@ export async function runClient(context: vscode.ExtensionContext, port: number)

// update status bar on file save
context.subscriptions.push(
vscode.workspace.onDidSaveTextDocument(() => {
if (extension.client) {
vscode.workspace.onDidSaveTextDocument(document => {
if (extension.client && !isCursorInsideLiquidJavaAnnotation(document)) {
updateStatusBar("loading");
}
})
Expand Down
43 changes: 43 additions & 0 deletions client/src/services/annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as vscode from "vscode";
import { LIQUIDJAVA_ANNOTATION_START, LJAnnotation } from "../utils/constants";

/**
* Returns the LiquidJava annotation containing the given position
*/
export function getActiveLiquidJavaAnnotation(document: vscode.TextDocument, position: vscode.Position): LJAnnotation | null {
const textUntilCursor = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
LIQUIDJAVA_ANNOTATION_START.lastIndex = 0;
let match: RegExpExecArray | null = null;
let lastAnnotationStart = -1;
let lastAnnotationName: LJAnnotation | null = null;
while ((match = LIQUIDJAVA_ANNOTATION_START.exec(textUntilCursor)) !== null) {
lastAnnotationStart = match.index;
lastAnnotationName = match[2] ? match[2] as LJAnnotation : null;
}
if (lastAnnotationStart === -1 || !lastAnnotationName) return null;

const fromLastAnnotation = textUntilCursor.slice(lastAnnotationStart);
let parenthesisDepth = 0;
let isInsideString = false;
for (let i = 0; i < fromLastAnnotation.length; i++) {
const char = fromLastAnnotation[i];
const previousChar = i > 0 ? fromLastAnnotation[i - 1] : "";
if (char === '"' && previousChar !== "\\") {
isInsideString = !isInsideString;
continue;
}
if (isInsideString) continue;
if (char === "(") parenthesisDepth++;
if (char === ")") parenthesisDepth--;
}
return parenthesisDepth > 0 ? lastAnnotationName : null;
}

/**
* Checks whether any cursor in the active editor is inside a LiquidJava annotation
*/
export function isCursorInsideLiquidJavaAnnotation(document: vscode.TextDocument): boolean {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri.toString() !== document.uri.toString()) return false;
return editor.selections.some(selection => Boolean(getActiveLiquidJavaAnnotation(document, selection.active)));
}
49 changes: 18 additions & 31 deletions client/src/services/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as vscode from "vscode";
import { extension } from "../state";
import type { LJVariable, LJContext, LJGhost, LJAlias } from "../types/context";
import { getSimpleName } from "../utils/utils";
import { LIQUIDJAVA_ANNOTATION_START, LJAnnotation } from "../utils/constants";
import { LJAnnotation } from "../utils/constants";
import { filterDuplicateVariables, filterInstanceVariables } from "./context";
import { isExtensionRunning } from "../extension";
import { getActiveLiquidJavaAnnotation } from "./annotation";

type CompletionItemOptions = {
name: string;
Expand Down Expand Up @@ -43,7 +44,22 @@ export function registerAutocomplete(context: vscode.ExtensionContext) {
});
return Array.from(uniqueItems.values());
},
}, '.', '"')
}, '.', '"'),
vscode.workspace.onDidChangeTextDocument(event => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri.toString() !== event.document.uri.toString()) return;
if (event.document.languageId !== "java" || event.contentChanges.length === 0) return;

// VS Code does not reliably invoke completion providers while editing string literals
// wait for selection to catch up with the document change and then retrigger completion
setTimeout(() => {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor || activeEditor.document.uri.toString() !== event.document.uri.toString()) return;
if (!isExtensionRunning()) return;
if (!getActiveLiquidJavaAnnotation(activeEditor.document, activeEditor.selection.active)) return;
void vscode.commands.executeCommand("editor.action.triggerSuggest");
}, 0);
})
);
}

Expand Down Expand Up @@ -212,35 +228,6 @@ function createCompletionItem({ name, kind, labelDetail, description, detail, do
return item;
}

function getActiveLiquidJavaAnnotation(document: vscode.TextDocument, position: vscode.Position): LJAnnotation | null {
const textUntilCursor = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
LIQUIDJAVA_ANNOTATION_START.lastIndex = 0;
let match: RegExpExecArray | null = null;
let lastAnnotationStart = -1;
let lastAnnotationName: LJAnnotation | null = null;
while ((match = LIQUIDJAVA_ANNOTATION_START.exec(textUntilCursor)) !== null) {
lastAnnotationStart = match.index;
lastAnnotationName = match[2] ? match[2] as LJAnnotation : null;
}
if (lastAnnotationStart === -1 || !lastAnnotationName) return null;

const fromLastAnnotation = textUntilCursor.slice(lastAnnotationStart);
let parenthesisDepth = 0;
let isInsideString = false;
for (let i = 0; i < fromLastAnnotation.length; i++) {
const char = fromLastAnnotation[i];
const previousChar = i > 0 ? fromLastAnnotation[i - 1] : "";
if (char === '"' && previousChar !== "\\") {
isInsideString = !isInsideString;
continue;
}
if (isInsideString) continue;
if (char === "(") parenthesisDepth++;
if (char === ")") parenthesisDepth--;
}
return parenthesisDepth > 0 ? lastAnnotationName : null;
}

function getReceiverBeforeDot(document: vscode.TextDocument, position: vscode.Position): string | null {
const prefix = document.lineAt(position.line).text.slice(0, position.character);
const match = prefix.match(/((?:old\s*\(\s*this\s*\))|(?:[A-Za-z_]\w*))\.\w*$/);
Expand Down
4 changes: 3 additions & 1 deletion client/src/services/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { updateStateMachine } from './state-machine';
import { SELECTION_DEBOUNCE_MS } from '../utils/constants';
import { getSelectionContextVariables, normalizeRange, updateErrorAtCursor } from './context';
import { normalizeFilePath, toRange } from '../utils/utils';
import { isCursorInsideLiquidJavaAnnotation } from './annotation';

let selectionTimeout: NodeJS.Timeout | null = null;

Expand All @@ -20,6 +21,7 @@ export function registerEvents(context: vscode.ExtensionContext) {
}),
vscode.workspace.onDidSaveTextDocument(async document => {
if (document.uri.scheme !== 'file' || document.languageId !== "java") return;
if (isCursorInsideLiquidJavaAnnotation(document)) return;
await updateStateMachine(document)
}),
vscode.window.onDidChangeTextEditorSelection(event => {
Expand Down Expand Up @@ -70,4 +72,4 @@ function handleContextUpdate(selection: vscode.Selection) {
extension.context.allVars = allVars;
updateErrorAtCursor();
extension.webview?.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor });
}
}
6 changes: 3 additions & 3 deletions client/src/services/status-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export function registerStatusBar(context: vscode.ExtensionContext) {
* @param notifyWebview Whether the webview should reflect this status update.
*/
export function updateStatusBar(status: ExtensionStatus, notifyWebview = status !== "loading") {
if (notifyWebview) {
extension.status = status;
extension.status = status;
if (notifyWebview)
extension.webview?.sendMessage({ type: "status", status });
}

const color = status === "stopped" || status === "crashed" ? "errorForeground" : "statusBar.foreground";
if (!extension.statusBar) return;
extension.statusBar.color = new vscode.ThemeColor(color);
Expand Down