Skip to content

add remove option on parser failed notification #181

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ This means faster parsing, faster language installation, and no need for
- Support for `node` v16, it might still work, but the extension now
requires `node` v18+.

## Fixed

- If local parser installation is corrupt, add 'Remove' option to notification.

# 0.6.1

## Added
Expand Down
2 changes: 0 additions & 2 deletions src/FileTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { Selection } from "./Selection";
import { getLanguageConfig } from "./configuration";
import { getLogger } from "./outputChannel";
import { parserFinishedInit } from "./extension";

function positionToPoint(pos: vscode.Position): Parser.Point {
return {
Expand Down Expand Up @@ -72,11 +71,10 @@
);
}

public static async new(

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Static async method 'new' has no 'await' expression

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Static async method 'new' has no 'await' expression

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Static async method 'new' has no 'await' expression

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Static async method 'new' has no 'await' expression

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Static async method 'new' has no 'await' expression

Check failure on line 74 in src/FileTree.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Static async method 'new' has no 'await' expression
language: Language,
document: vscode.TextDocument
): Promise<Result<FileTree, unknown>> {
await parserFinishedInit;
const parser = new Parser();
const logger = getLogger();
try {
Expand Down
81 changes: 53 additions & 28 deletions src/Installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import * as vscode from "vscode";
import { ExecException, ExecOptions, exec } from "child_process";
import { Result, err, ok } from "./result";
import { existsSync } from "fs";
import { existsSync, rmSync } from "fs";
import { getLogger } from "./outputChannel";
import { mkdir } from "fs/promises";
import { parserFinishedInit } from "./extension";
import which from "which";

const NPM_INSTALL_URL = "https://nodejs.org/en/download";
Expand All @@ -22,7 +21,7 @@
return path.resolve(path.join(parsersDir, parserName, "bindings", "node", "index.js"));
}

export async function loadParser(

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Async function 'loadParser' has no 'await' expression

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Async function 'loadParser' has no 'await' expression

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Async function 'loadParser' has no 'await' expression

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Async function 'loadParser' has no 'await' expression

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Async function 'loadParser' has no 'await' expression

Check failure on line 24 in src/Installer.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Async function 'loadParser' has no 'await' expression
parsersDir: string,
parserName: string,
subdirectory?: string
Expand All @@ -34,34 +33,33 @@
const msg = `Expected parser directory doesn't exist: ${bindingsDir}`;
logger.log(msg);
return err(msg);
} else {
await parserFinishedInit;
try {
logger.log(`Loading parser from ${bindingsDir}`);
}

// using dynamic import causes issues on windows
// make sure to test well on windows before changing this
// TODO(02/11/24): change to dynamic import
// let { default: language } = (await import(bindingsDir)) as { default: Language };
try {
logger.log(`Loading parser from ${bindingsDir}`);

// eslint-disable-next-line @typescript-eslint/no-var-requires
let language = require(bindingsDir) as Language;
// using dynamic import causes issues on windows
// make sure to test well on windows before changing this
// TODO(02/11/24): change to dynamic import
// let { default: language } = (await import(bindingsDir)) as { default: Language };

logger.log(`Got language: ${JSON.stringify(Object.keys(language))}`);
// eslint-disable-next-line @typescript-eslint/no-var-requires
let language = require(bindingsDir) as Language;

if (subdirectory !== undefined) {
logger.log(`Loading subdirectory: ${subdirectory}`);
// @ts-expect-error we know this is a language
language = language[subdirectory] as Language;
logger.log(`Got language: ${JSON.stringify(Object.keys(language))}`);

logger.log(`Got subdirectory language: ${JSON.stringify(Object.keys(language))}`);
}
if (subdirectory !== undefined) {
logger.log(`Loading subdirectory: ${subdirectory}`);
// @ts-expect-error we know this is a language
language = language[subdirectory] as Language;

return ok(language);
} catch (error) {
logger.log(`Failed to load ${bindingsDir} > ${JSON.stringify(error)}`);
return err(`Failed to load ${bindingsDir} > ${JSON.stringify(error)}`);
logger.log(`Got subdirectory language: ${JSON.stringify(Object.keys(language))}`);
}

return ok(language);
} catch (error) {
logger.log(`Failed to load ${bindingsDir} > ${JSON.stringify(error)}`);
return err(`Failed to load ${bindingsDir} > ${JSON.stringify(error)}`);
}
}

Expand Down Expand Up @@ -183,11 +181,16 @@
});
}

export type GetLanguageError = {
cause: "downloadFailed" | "loadFailed";
msg: string;
};

export async function getLanguage(
parsersDir: string,
languageId: string,
autoInstall = false
): Promise<Result<Language | undefined, string>> {
): Promise<Result<Language | undefined, GetLanguageError>> {
const logger = getLogger();

const ignoredLanguageIds = configuration.getIgnoredLanguageIds();
Expand All @@ -202,8 +205,6 @@
const npm = "npm";
const treeSitterCli = configuration.getTreeSitterCliPath();

await parserFinishedInit;

if (!existsSync(parserPackagePath)) {
const doInstall = autoInstall
? "Yes"
Expand Down Expand Up @@ -265,7 +266,7 @@
const msg = `Failed to download/build parser for language ${languageId} > ${downloadResult.result}`;

logger.log(msg);
return err(msg);
return err({ cause: "downloadFailed", msg });
}
}

Expand All @@ -274,9 +275,33 @@
const msg = `Failed to load parser for language ${languageId} > ${loadResult.result}`;

logger.log(msg);
return err(msg);
return err({ cause: "loadFailed", msg });
}

logger.log(`Successfully loaded parser for language ${languageId}`);
return ok(loadResult.result);
}

export async function askRemoveLanguage(parsersDir: string, languageId: string, msg: string): Promise<void> {
const doRemove = await vscode.window.showErrorMessage(
`Failed to load parser for ${languageId}: ${msg}`,
"Remove",
"Ok"
);

if (doRemove === "Remove") {
removeLanguage(parsersDir, languageId);
}
}

export function removeLanguage(parsersDir: string, languageId: string): void {
const logger = getLogger();

const { parserName } = configuration.getLanguageConfig(languageId);
const parserPackagePath = getAbsoluteParserDir(parsersDir, parserName);

if (existsSync(parserPackagePath)) {
rmSync(parserPackagePath, { recursive: true, force: true });
}
logger.log(`Removed parser '${parserPackagePath}'`);
}
25 changes: 16 additions & 9 deletions src/editor/CodeBlocksEditorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,24 @@ export class CodeBlocksEditorProvider implements vscode.CustomTextEditorProvider
let language = await Installer.getLanguage(this.extensionParsersDirPath, languageId);

while (language.status !== "ok") {
const items =
language.result.cause === "loadFailed"
? (["Remove", "Ok"] as const)
: (["Retry", "Ok"] as const);

const choice = await vscode.window.showErrorMessage(
`Parser installation failed: ${language.result}`,
"Retry",
"Ok"
`Parser installation failed: ${JSON.stringify(language.result)}`,
...items
);
if (choice !== "Retry") {

if (choice === "Remove") {
Installer.removeLanguage(this.extensionParsersDirPath, languageId);
return;
} else if (choice === "Retry") {
language = await Installer.getLanguage(this.extensionParsersDirPath, languageId);
} else {
return;
}

language = await Installer.getLanguage(this.extensionParsersDirPath, languageId);
}

if (language.result === undefined) {
Expand All @@ -54,9 +62,8 @@ export class CodeBlocksEditorProvider implements vscode.CustomTextEditorProvider
}
const fileTreeResult = await FileTree.new(language.result, document);
if (fileTreeResult.status === "err") {
await vscode.window.showErrorMessage(
`Failed to load parser for ${languageId}: ${JSON.stringify(fileTreeResult.result)}`
);
const msg = JSON.stringify(fileTreeResult.result);
await Installer.askRemoveLanguage(this.extensionParsersDirPath, languageId, msg);
return;
}

Expand Down
47 changes: 34 additions & 13 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import * as BlockMode from "./BlockMode";
import * as configuration from "./configuration";
import * as vscode from "vscode";
import * as Installer from "./Installer";

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Imports should be sorted alphabetically

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Imports should be sorted alphabetically

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Imports should be sorted alphabetically

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest)

Imports should be sorted alphabetically

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Imports should be sorted alphabetically

Check warning on line 4 in src/extension.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Imports should be sorted alphabetically
import { CodeBlocksEditorProvider } from "./editor/CodeBlocksEditorProvider";
import { FileTree } from "./FileTree";
import { TreeViewer } from "./TreeViewer";
import { getLanguage } from "./Installer";
import { getLogger } from "./outputChannel";
import { join } from "path";
import { state } from "./state";

export const parserFinishedInit = Promise.resolve();

async function reopenWithCodeBocksEditor(): Promise<void> {
const activeTabInput = vscode.window.tabGroups.activeTabGroup.activeTab?.input as {
[key: string]: unknown;
Expand Down Expand Up @@ -45,25 +44,47 @@
}

const activeDocument = editor.document;
const language = await getLanguage(parsersDir, activeDocument.languageId);
if (language.status === "err" || language.result === undefined) {
if (language.status === "err") {
void vscode.window.showErrorMessage(`Failed to get language: ${language.result}`);
} else {
logger.log(`No language found for ${activeDocument.languageId}`);
const languageId = activeDocument.languageId;
const language = await Installer.getLanguage(parsersDir, languageId);

// sup-optimal conditional to make tsc happy
// tl;dr this is handling logic for 'language not received' scenarios
if (language.result === undefined || language.status === "err") {
if (language.status === "ok") {
logger.log(`No language found for ${languageId}`);
return undefined;
}

return undefined;
switch (language.result.cause) {
case "downloadFailed": {
const doIgnore = await vscode.window.showErrorMessage(
`Failed to download language: ${language.result.msg}`,
"Add to ignore",
"Ok"
);

if (doIgnore === "Add to ignore") {
// fail silently if we can't add to ignore list
// we don't want to have two consecutive error messages
await configuration.addIgnoredLanguageId(languageId);
}

return undefined;
}

case "loadFailed": {
await Installer.askRemoveLanguage(parsersDir, languageId, language.result.msg);
return undefined;
}
}
}

const tree = await FileTree.new(language.result, activeDocument);
if (tree.status === "ok") {
return tree.result;
}

void vscode.window.showErrorMessage(
`Failed to load parser for ${activeDocument.languageId}: ${JSON.stringify(tree.result)}`
);
await Installer.askRemoveLanguage(parsersDir, languageId, JSON.stringify(tree.result));

return undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/suite/Installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function testParser(language: string, content?: string): Promise<vo
// fail the test if the parser could not be installed
const result = await Installer.getLanguage("test-parsers", language, true);
if (result.status === "err") {
throw new Error(`Failed to install language: ${result.result}`);
throw new Error(`Failed to install language: ${JSON.stringify(result.result)}`);
}

// check the language can be set
Expand Down
Loading