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

39 vscode extension better status bar information #48

9 changes: 8 additions & 1 deletion src/ast/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{
};

use ariadne::{Cache, Report, ReportBuilder, ReportKind, Source};
use lsp_types::{DiagnosticTag, VersionedTextDocumentIdentifier};
use lsp_types::{DiagnosticTag, TextDocumentIdentifier, VersionedTextDocumentIdentifier};
use pathdiff::diff_paths;

use crate::pretty::{Doc, SimplePretty};
Expand Down Expand Up @@ -157,6 +157,13 @@ impl Files {
self.files.iter().find(|file| &file.path == path)
}

pub fn find_uri(&self, document_id: TextDocumentIdentifier) -> Option<&Arc<StoredFile>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this method necessary? Why not just call find(SourceFilePath::Lsp(ident))?

self.files.iter().find(|file| match &file.path {
SourceFilePath::Lsp(ident) => ident.uri == document_id.uri,
_ => false,
})
}

pub fn char_span(&self, span: Span) -> CharSpan {
self.get(span.file).unwrap().char_span(span)
}
Expand Down
30 changes: 22 additions & 8 deletions src/servers/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ impl LspServer {
})
.id;
drop(files);
self.clear_all().map_err(VerifyError::ServerError)?;
self.clear_file_information(&file_id)
.map_err(VerifyError::ServerError)?;
let result = verify(self, &[file_id]);
let res = match &result {
Ok(_) => Response::new_ok(id, Value::Null),
Expand Down Expand Up @@ -195,9 +196,22 @@ impl LspServer {
Ok(None)
}
"textDocument/didClose" => {
let _params: DidCloseTextDocumentParams =
let params: DidCloseTextDocumentParams =
notification.extract("textDocument/didClose")?;
// TODO: remove file?

let file_id = self
.files
.lock()
.unwrap()
.find_uri(params.text_document.clone())
.unwrap_or_else(|| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Possibly just turn this into an .expect("Could not find file id for document"). The unwrap_or_else with a panic is a bit weird to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Clippy complains about the expect and suggests the unwrap_or_else version. I think this is why the unwrap_or_else version is previously preferred in the run_server method as well.

panic!(
"Could not find file id for document {:?}",
params.text_document
)
})
.id;
self.clear_file_information(&file_id)?;
Ok(None)
}
_ => Ok(Some(notification)),
Expand Down Expand Up @@ -297,14 +311,14 @@ impl LspServer {
Ok(())
}

fn clear_all(&mut self) -> Result<(), ServerError> {
for diags in self.diagnostics.values_mut() {
diags.clear();
fn clear_file_information(&mut self, file_id: &FileId) -> Result<(), ServerError> {
if let Some(diag) = self.diagnostics.get_mut(file_id) {
diag.clear();
}
for explanations in self.vc_explanations.values_mut() {
if let Some(explanations) = self.vc_explanations.get_mut(file_id) {
explanations.clear();
}
self.statuses.clear();
self.statuses.retain(|span, _| span.file != *file_id);
self.publish_diagnostics()?;
self.publish_verify_statuses()?;
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions vscode-ext/src/CaesarClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { WalkthroughComponent } from "./WalkthroughComponent";
import Logger from "./Logger";

export enum ServerStatus {
NotStarted,
Stopped,
Starting,
Ready,
FailedToStart,
Verifying,
Finished
}

export enum VerifyResult {
Expand Down Expand Up @@ -398,7 +398,7 @@ export class CaesarClient {
this.notifyStatusUpdate(ServerStatus.Verifying);
try {
await this.client.sendRequest('custom/verify', { text_document: documentItem });
this.notifyStatusUpdate(ServerStatus.Finished);
this.notifyStatusUpdate(ServerStatus.Ready);
this.logger.info("Client: completed verification.", document.uri);
await this.walkthrough.setVerifiedHeyVL(true);
} catch (error) {
Expand Down
10 changes: 9 additions & 1 deletion vscode-ext/src/ComputedPreComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ export class ComputedPreComponent {
}
}));

verifier.context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((document) => {
const documentIdentifier = { uri: document.uri.toString() };
this.computedPres.remove(documentIdentifier);
this.render();
}));

// listen to custom/computedPre notifications
verifier.client.onComputedPre((update) => {
this.computedPres.insert(update.document, update.pres);
Expand All @@ -64,14 +70,16 @@ export class ComputedPreComponent {

// clear all information when a new verification task is started
verifier.client.onStatusUpdate((status) => {
if (status !== ServerStatus.Finished) {
if (status == ServerStatus.Verifying) {
for (const [_document, results] of this.computedPres.entries()) {
results.length = 0;
}
this.render();
}
});



// TODO: listen to content changes to remove lines?
}

Expand Down
9 changes: 8 additions & 1 deletion vscode-ext/src/GutterStatusComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GutterInformationViewConfig } from "./Config";
import { ServerStatus, VerifyResult } from "./CaesarClient";
import { DocumentMap, Verifier } from "./Verifier";
import { ConfigurationConstants } from "./constants";
import { TextDocumentIdentifier } from "vscode-languageclient";

export class GutterStatusComponent {

Expand Down Expand Up @@ -40,7 +41,7 @@ export class GutterStatusComponent {

// listen to status and verify updates
verifier.client.onStatusUpdate((status) => {
if (status !== ServerStatus.Finished) {
if (status == ServerStatus.Verifying) {
for (const [_document, results] of this.status.entries()) {
results.length = 0;
}
Expand All @@ -52,6 +53,12 @@ export class GutterStatusComponent {
this.status.insert(document, results);
this.render();
});

verifier.context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((document) => {
const documentIdentifier: TextDocumentIdentifier = { uri: document.uri.toString() };
this.status.remove(documentIdentifier);
this.render();
}));
}

render() {
Expand Down
171 changes: 152 additions & 19 deletions vscode-ext/src/StatusBarComponent.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { StatusBarItem } from "vscode";
import { StatusBarItem, Range } from "vscode";
import * as vscode from 'vscode';
import { StatusBarViewConfig } from "./Config";
import { ServerStatus } from "./CaesarClient";
import { Verifier } from "./Verifier";
import { ServerStatus, VerifyResult } from "./CaesarClient";
import { DocumentMap, Verifier } from "./Verifier";
import { ConfigurationConstants } from "./constants";
import { TextDocumentIdentifier } from "vscode-languageclient";


const runningTooltipMenu =
new vscode.MarkdownString(
"[Stop Caesar](command:caesar.stopServer)\n\n" +
"[Restart Caesar](command:caesar.restartServer)"
, true);

const stoppedTooltipMenu =
new vscode.MarkdownString(
"[Start Caesar](command:caesar.startServer)", true);

export class StatusBarComponent {

private enabled: boolean;
private status: ServerStatus = ServerStatus.Stopped;
private verifyStatus: DocumentMap<[Range, VerifyResult][]> = new DocumentMap();
private serverStatus: ServerStatus = ServerStatus.NotStarted;
private view: StatusBarItem;

constructor(verifier: Verifier) {
Expand All @@ -18,11 +31,6 @@ export class StatusBarComponent {
this.view.text = "Caesar";
this.view.command = "caesar.showOutput";

this.view.tooltip = new vscode.MarkdownString(
"[Restart Caesar](command:caesar.restartServer)\n\n" +
"[Start Caesar](command:caesar.startServer)\n\n" +
"[Stop Caesar](command:caesar.stopServer)",
true);

// render if enabled
this.enabled = StatusBarViewConfig.get(ConfigurationConstants.showStatusBar);
Expand All @@ -38,36 +46,161 @@ export class StatusBarComponent {

// listen to verifier updates
verifier.client.onStatusUpdate((status) => {
this.status = status;
this.serverStatus = status;
this.render();
});

verifier.client.onVerifyResult((document, results) => {
this.verifyStatus.insert(document, results);
this.render();
});

verifier.context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((document) => {
const documentIdentifier: TextDocumentIdentifier = { uri: document.uri.toString() };
this.verifyStatus.remove(documentIdentifier);
this.render();
}));

verifier.context.subscriptions.push(vscode.window.onDidChangeVisibleTextEditors(() => {
this.render();
}));

verifier.context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(() => {
this.render();
}));
}

render() {
let viewText = "";
let command = "";
let tooltipMenuText = new vscode.MarkdownString();
let tooltipStatusText = new vscode.MarkdownString();
if (this.enabled) {
switch (this.status) {

switch (this.serverStatus) {
case ServerStatus.NotStarted:
viewText = "$(debug-start) Caesar Inactive";
command = "caesar.startServer";
tooltipMenuText = stoppedTooltipMenu;
break;
case ServerStatus.Stopped:
this.view.text = "$(debug-stop) Et tu, Brute?";
viewText = "$(debug-start) Et tu, Brute?";
command = "caesar.startServer";
tooltipMenuText = stoppedTooltipMenu;
break;
case ServerStatus.FailedToStart:
this.view.text = "$(warning) Failed to start Caesar";
viewText = "$(warning) Failed to start Caesar";
command = "caesar.startServer";
tooltipMenuText = stoppedTooltipMenu;
break;
case ServerStatus.Starting:
this.view.text = "$(sync~spin) Starting Caesar...";
viewText = "$(loading~spin) Starting Caesar...";
command = "caesar.showOutput"
break;
case ServerStatus.Ready:
this.view.text = "$(check) Caesar Ready";
[viewText, tooltipStatusText] = this.getReadyStatusView();
command = "caesar.showOutput";
tooltipMenuText = runningTooltipMenu;
break;
case ServerStatus.Verifying:
this.view.text = "$(sync~spin) Verifying...";
break;
case ServerStatus.Finished:
this.view.text = "$(check) Verified";
viewText = "$(sync~spin) Verifying...";
command = "caesar.showOutput"
break;
}

this.view.text = viewText;
this.view.command = command;
this.renderTooltip(tooltipMenuText, tooltipStatusText);
this.view.show();
} else {
this.view.hide();
}
}

/// Renders the tooltip by combining the given menu and status text.
private renderTooltip(tooltipMenuText: vscode.MarkdownString, tooltipStatusText: vscode.MarkdownString) {
if (tooltipStatusText.value === "") {
this.view.tooltip = tooltipMenuText;
this.view.tooltip.isTrusted = true;
return;
}
this.view.tooltip = new vscode.MarkdownString(tooltipMenuText.value + "\n\n --- \n" + tooltipStatusText.value, true);
this.view.tooltip.isTrusted = true;
}


/// Returns the status view text and tooltip for the ready status by aggregating the verification results for each visible editor.
private getReadyStatusView(): [string, vscode.MarkdownString] {
let viewText = "";
let tooltipString = new vscode.MarkdownString("", true);

let someError = false;
let someVerified = false;

const editorsWithResults = vscode.window.visibleTextEditors.filter((editor) => {
return (this.verifyStatus.get({ uri: editor.document.uri.toString() }) ?? []).length !== 0; // And only files with results
});

if (editorsWithResults.length === 0) {
return ["$(thumbsup-filled) Caesar Ready", new vscode.MarkdownString("Verify some HeyVL files!", true)];
}

for (const editor of editorsWithResults) {
const document_id: TextDocumentIdentifier = { uri: editor.document.uri.toString() };

const results = this.verifyStatus.get(document_id);

if (results === undefined) throw new Error("No verify results found for document: " + document_id);

let verified = 0;
let failed = 0;
let unknown = 0;

for (const [_, result] of results) {
switch (result) {
case VerifyResult.Verified:
verified++;
break;
case VerifyResult.Failed:
failed++;
break;
case VerifyResult.Unknown:
unknown++;
break;
}
}

if (failed > 0 || unknown > 0) {
someError = true;
}
if (verified > 0) {
someVerified = true;
}

tooltipString.appendMarkdown(`${vscode.Uri.parse(document_id.uri).path}: $(error) ${failed} $(question) ${unknown}` + "\n\n --- \n");
}

// Remove the last newline and separator
tooltipString.value.trimEnd();

if (!someError && someVerified) {
// No error and at least one verified implies everything is verified.
viewText = "$(pass) Verified!";
tooltipString = new vscode.MarkdownString("No errors found", true);
} else if (someError) {
// At least one verified, but some errors
viewText = "$(warning) Verification Errors";
} else if (!someError && !someVerified) {
// No error and no verified implies the file is either empty or there are syntax errors.
viewText = "$(error) Invalid File";
tooltipString = new vscode.MarkdownString("Syntax error or empty file!", true);
}

return [viewText, tooltipString];

}

}



4 changes: 4 additions & 0 deletions vscode-ext/src/Verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export class DocumentMap<T> {
public entries(): [TextDocumentIdentifier, T][] {
return Array.from(this.map.values());
}

public remove(document_id: TextDocumentIdentifier) {
this.map.delete(document_id.uri.toString());
}
}

export class Verifier {
Expand Down
Loading