Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Use inlay hints to render captures #34

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
125 changes: 77 additions & 48 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,65 @@ class EffektRunCodeLensProvider implements vscode.CodeLensProvider {
}
}

let debugOutputChannel = vscode.window.createOutputChannel("Effekt DEBUG");
function log(message: string) {
debugOutputChannel.appendLine(message);
}

interface InlayHintCache {
[uri: string]: vscode.InlayHint[];
}

const inlayHintCache: InlayHintCache = {};

class EffektCapturesProvider implements vscode.InlayHintsProvider {
public async provideInlayHints(document: vscode.TextDocument, range: vscode.Range): Promise<vscode.InlayHint[]> {
log("Inlay hints requested for: " + document.uri.toString() + " & range: " + JSON.stringify(range));

try {
const result = await client.sendRequest(ExecuteCommandRequest.type, {
command: "inferredCaptures",
arguments: [{ uri: document.uri.toString() }]
}) as { location: vscode.Location, captureText: string }[];

log("Inlay hints result: " + JSON.stringify(result));

if (!result) {
log("No results returned.");
return inlayHintCache[document.uri.toString()];
}

inlayHintCache[document.uri.toString()] = [];
for (const response of result) {
log("processing a single response: " + JSON.stringify(response))
if (response.location.uri.toString() === document.uri.toString()) {
log("... URI correct => creating a hint!")
const position = response.location.range.start;

// Truncate long captures ourselves.
// TODO: Does this make sense? Shouldn't we at least show the first one?
// TODO: Can the backend send them in a list so that we can have a somewhat stable (alphabetic?) order?
const hintText = response.captureText.length > 30 ? "{…}" : response.captureText;
Comment on lines +112 to +115
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VSCode actively truncates inlay hints that are too long (something like 34 chars?), therefore it's better that we try to truncate ourselves. We can bike shed the proper constant here, but should we show at least some captures like {async, io, Exception, ...} or should we just go to {...} in such a case (and rely only on the hover like in the screenshot above?)

const hint = new vscode.InlayHint(position, hintText, vscode.InlayHintKind.Type);

// This tooltip is useful when there are a lot of captures.
hint.tooltip = new vscode.MarkdownString(`Captures: \`${response.captureText}\``);
hint.paddingRight = true;
hint.paddingLeft = false;
inlayHintCache[document.uri.toString()].push(hint);
}
}

} catch (error) {
log("Error during inlay hints request: " + JSON.stringify(error));
vscode.window.showErrorMessage("An error occurred while fetching inlay hints.");
inlayHintCache[document.uri.toString()] = [];
}

return inlayHintCache[document.uri.toString()];
}
}

export async function activate(context: vscode.ExtensionContext) {
effektManager = new EffektManager();

Expand All @@ -84,6 +143,8 @@ export async function activate(context: vscode.ExtensionContext) {

registerCommands(context);

const config = vscode.workspace.getConfiguration("effekt");

// Register the CodeLens provider
context.subscriptions.push(
vscode.languages.registerCodeLensProvider(
Expand All @@ -96,6 +157,21 @@ export async function activate(context: vscode.ExtensionContext) {
)
);

// Conditionally register the Captures provider
//if (config.get<boolean>("showCaptures")) {
context.subscriptions.push(
vscode.languages.registerInlayHintsProvider(
{ language: 'effekt', scheme: 'file' },
new EffektCapturesProvider()
),
vscode.languages.registerInlayHintsProvider(
{ language: 'literate effekt', scheme: 'file' },
new EffektCapturesProvider()
)
);
//}


// Clean up REPL when closed
context.subscriptions.push(
vscode.window.onDidCloseTerminal(terminal => {
Expand All @@ -105,8 +181,6 @@ export async function activate(context: vscode.ExtensionContext) {
})
);

const config = vscode.workspace.getConfiguration("effekt");

let serverOptions: ServerOptions;

if (config.get<boolean>("debug")) {
Expand Down Expand Up @@ -177,7 +251,7 @@ export async function activate(context: vscode.ExtensionContext) {
})

// the decorations themselves don't have styles. Only the added before-elements.
const captureDecoration = vscode.window.createTextEditorDecorationType({})
// const captureDecoration = vscode.window.createTextEditorDecorationType({})

// based on https://github.com/microsoft/vscode-extension-samples/blob/master/decorator-sample/src/extension.ts
let timeout: NodeJS.Timeout;
Expand All @@ -188,47 +262,6 @@ export async function activate(context: vscode.ExtensionContext) {
timeout = setTimeout(updateHoles, 50);
}

function updateCaptures() {

if (!editor) { return; }

if (!config.get<boolean>("showCaptures")) { return; }

client.sendRequest(ExecuteCommandRequest.type, { command: "inferredCaptures", arguments: [{
uri: editor.document.uri.toString()
}]}).then(
(result : [{ location: vscode.Location, captureText: string }]) => {
if (!editor) { return; }

let captureAnnotations: vscode.DecorationOptions[] = []

if (result == null) return;

result.forEach(response => {
if (!editor) { return; }
const loc = response.location
if (loc.uri != editor.document.uri) return;

captureAnnotations.push({
range: loc.range,
renderOptions: {
before: {
contentText: response.captureText,
backgroundColor: "rgba(170,210,255,0.3)",
color: "rgba(50,50,50,0.5)",
fontStyle: "italic",
margin: "0 0.5em 0 0.5em"
}
}
})
})

if (!editor) { return; }
return editor.setDecorations(captureDecoration, captureAnnotations)
}
);
}

const holeRegex = /<>|<{|}>/g

/**
Expand Down Expand Up @@ -267,10 +300,6 @@ export async function activate(context: vscode.ExtensionContext) {
}
}, null, context.subscriptions);

vscode.workspace.onDidSaveTextDocument(ev => {
setTimeout(updateCaptures, 50)
})

scheduleDecorations();

context.subscriptions.push(client.start());
Expand Down