Skip to content

Commit

Permalink
feat: markdown linting (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcanouil authored Feb 21, 2025
1 parent 371005f commit 275993f
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- feat: automatic markdown linting via `markdownlint`.
- feat: lazy extension dependencies.

## 0.11.0 (2025-02-20)

- feat: first release of the Quarto Wizard extension, but not yet 1.0.0.
Expand Down
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,21 @@
"default": "ask",
"markdownDescription": "Ask for confirmation before installing an extension. `ask` to ask for confirmation, `never` to always confirm and never ask again."
},
"quartoWizard.log.level": {
"quartoWizard.lint.trigger": {
"order": 4,
"scope": "resource",
"type": "string",
"enum": [
"save",
"type"
],
"default": "save",
"markdownDescription": "Run the markdown linter on save (`save`) or on type (`type`)."
},
"quartoWizard.log.level": {
"order": 20,
"scope": "resource",
"type": "string",
"enum": [
"error",
"warn",
Expand Down
70 changes: 70 additions & 0 deletions src/commands/lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as vscode from "vscode";
import { QW_LOG } from "../constants";

const kMarkDownLintExtension = "DavidAnson.vscode-markdownlint";

/**
* Lints the currently active text editor if the document language is "quarto".
*
* This function performs the following steps:
* 1. Checks if there is an active text editor and if the document language is "quarto".
* 2. Activates the "DavidAnson.vscode-markdownlint" extension.
* 3. Changes the document language to "markdown".
* 4. Toggles markdown linting twice to ensure it is enabled.
* 5. Changes the document language back to "quarto".
*/
function lint() {
if (!vscode.extensions.getExtension(kMarkDownLintExtension)) {
QW_LOG.appendLine(`The '${kMarkDownLintExtension}' extension is not installed.`);
return;
}
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === "quarto") {
vscode.languages
.setTextDocumentLanguage(editor.document, "markdown")
.then(() => {
vscode.commands.executeCommand("markdownlint.toggleLinting");
vscode.commands.executeCommand("markdownlint.toggleLinting"); // Toggle twice to ensure linting is enabled
})
.then(() => {
vscode.languages.setTextDocumentLanguage(editor.document, "quarto");
});
}
}

/**
* Registers event listeners to trigger the linting process based on the user's configuration.
*
* The function reads the `quartoWizard.lint.trigger` configuration setting to determine when to trigger linting.
* It supports two triggers:
* - "save": Linting is triggered when a Quarto document is saved.
* - "type": Linting is triggered when a Quarto document is modified.
*
* Depending on the trigger setting, the appropriate event listener is registered:
* - For "save", it listens to the `onDidSaveTextDocument` event.
* - For "type", it listens to the `onDidChangeTextDocument` event.
*
* When the specified event occurs, and the document's language ID is "quarto", the `quartoWizard.lint` command is executed.
*/
export function lintOnEvent() {
if (!vscode.extensions.getExtension(kMarkDownLintExtension)) {
QW_LOG.appendLine(`The '${kMarkDownLintExtension}' extension is not installed.`);
return;
}
const config = vscode.workspace.getConfiguration("quartoWizard.lint", null);
const lintOn = config.get<string>("trigger");
if (lintOn === "save") {
vscode.workspace.onDidSaveTextDocument((document) => {
if (document.languageId === "quarto") {
lint();
}
});
}
if (lintOn === "type") {
vscode.workspace.onDidChangeTextDocument((event) => {
if (event.document.languageId === "quarto") {
lint();
}
});
}
}
7 changes: 7 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { installQuartoExtensionCommand } from "./commands/installQuartoExtension
import { newQuartoReprexCommand } from "./commands/newQuartoReprex";
import { ExtensionsInstalled } from "./ui/extensionsInstalled";
import { getExtensionsDetails } from "./utils/extensionDetails";
import { activateExtensions } from "./utils/activate";
import { lintOnEvent } from "./commands/lint";

/**
* This method is called when the extension is activated.
Expand Down Expand Up @@ -38,6 +40,11 @@ export function activate(context: vscode.ExtensionContext) {
);

new ExtensionsInstalled(context);

if (vscode.window.activeTextEditor?.document.languageId === "quarto") {
activateExtensions(["DavidAnson.vscode-markdownlint"], context);
lintOnEvent();
}
}

/**
Expand Down
66 changes: 66 additions & 0 deletions src/utils/activate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as vscode from "vscode";
import { QW_LOG } from "../constants";

/**
* Prompts the user to install a specified extension if it is not already installed.
* The user's choice is stored in the global state to avoid prompting repeatedly.
*
* @param extensionId - The ID of the extension to be installed.
* @param context - The extension context which provides access to the global state.
* @returns A promise that resolves when the prompt handling is complete.
*
* The function performs the following actions based on the user's choice:
* - "Install Now": Initiates the installation of the extension.
* - "Maybe Later": Logs the user's choice and sets a flag to prompt again later.
* - "Don't Ask Again": Logs the user's choice and sets a flag to avoid future prompts.
*/
async function promptInstallExtension(extensionId: string, context: vscode.ExtensionContext): Promise<void> {
const kPromptInstallExtension = "PromptInstallExtension";
const prompt = context.globalState.get<boolean>(`${kPromptInstallExtension}.${extensionId}`);
if (prompt === false) {
return;
}
const choice = await vscode.window.showInformationMessage(
`Extension '${extensionId}' is not installed. Would you like to install it?`,
"Install Now",
"Maybe Later",
"Don't Ask Again"
);
switch (choice) {
case "Install Now":
await vscode.commands.executeCommand("workbench.extensions.installExtension", extensionId);
QW_LOG.appendLine(`${extensionId} installation initiated.`);
break;
case "Maybe Later":
QW_LOG.appendLine(`User chose to install ${extensionId} later.`);
context.globalState.update(`${kPromptInstallExtension}.${extensionId}`, true);
break;
case "Don't Ask Again":
QW_LOG.appendLine(`User chose not to be asked again about ${extensionId}.`);
context.globalState.update(`${kPromptInstallExtension}.${extensionId}`, false);
break;
}
}

/**
* Activates a list of VS Code extensions.
*
* @param extensions - An array of extension IDs to activate.
* @param context - The VS Code extension context.
* @returns A promise that resolves when all extensions have been processed.
*/
export async function activateExtensions(extensions: string[], context: vscode.ExtensionContext): Promise<void> {
extensions.forEach(async (extensionId) => {
const extension = await vscode.extensions.getExtension(extensionId);
if (extension) {
if (!extension.isActive) {
console.log(`Activating ${extensionId}...`);
await extension.activate();
}
QW_LOG.appendLine(`${extensionId} activated.`);
} else {
QW_LOG.appendLine(`Failed to activate ${extensionId}.`);
await promptInstallExtension(extensionId, context);
}
});
}

0 comments on commit 275993f

Please # to comment.