Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created August 17, 2023 10:50
Show Gist options
  • Save mizchi/21386be2d4535aeabcc504908e217e61 to your computer and use it in GitHub Desktop.
Save mizchi/21386be2d4535aeabcc504908e217e61 to your computer and use it in GitHub Desktop.
import * as vscode from "vscode";
import { type CodeBlock, extractCodeBlocks } from "./markdown";
const MDXV_SCHEMA = "mdxv";
type CodeBlockWithMetadata = CodeBlock & {
vFileName: string;
vContent: string;
};
export async function activate(context: vscode.ExtensionContext) {
const virtualContents = new Map<string, string>();
const diagnosticCollection =
vscode.languages.createDiagnosticCollection("mdx");
context.subscriptions.push(diagnosticCollection);
// preload on activation
context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument((document) => {
if (document.languageId !== "mdx") return;
refresh(document.fileName, document.getText());
}),
);
// create document
context.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(MDXV_SCHEMA, {
onDidChange(ev) {
console.log("[mdxv:onDidChange]", ev);
return {
dispose() {
console.log("[mdxv:onDidChange:dispose]", ev);
},
};
},
provideTextDocumentContent: (uri) => {
return virtualContents.get(uri.path);
},
}),
);
// auto complete
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider("mdx", {
async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
_token: vscode.CancellationToken,
context: vscode.CompletionContext,
) {
const { contents } = refresh(document.fileName, document.getText());
const offset = document.offsetAt(position);
const currentBlock = contents.find(
({ codeRange: [start, end] }) => start <= offset && offset <= end,
);
if (!currentBlock) return [];
const uri = vscode.Uri.parse(
`${MDXV_SCHEMA}://${currentBlock.vFileName}`,
);
},
}),
);
const current = vscode.window.activeTextEditor;
if (current?.document.languageId === "mdx") {
const doc = current.document;
if (doc.languageId !== "mdx") return;
refresh(doc.fileName, doc.getText());
}
return;
function refresh(fileName: string, raw: string) {
console.log("[mdxv:refresh]", fileName, raw.length);
const blocks = extractCodeBlocks(raw);
const contents = blocks.map<CodeBlockWithMetadata>((block, idx) => {
const vFileName = getVirtualFileName(fileName, idx.toString());
const maskedPrefix = [...raw.slice(0, block.codeRange[0])]
.map((c) => (c === "\n" ? c : " "))
.join("");
const vContent = maskedPrefix + block.content;
virtualContents.set(vFileName, vContent);
return {
...block,
vFileName,
vContent,
};
});
return {
contents,
};
}
function getVirtualFileName(originalFileName: string, id: string) {
return `${originalFileName}_${id}.ts`;
}
}
export function deactivate() {}
const regex = new RegExp(/```(?<type>[^\n]*)?\n(?<content>[.\s\S\n]*?)\n```/gm);
type Range = [from: number, to: number];
export type CodeBlock = {
blockRange: Range;
codeRange: Range;
content: string;
id: string | undefined;
codeType: string;
}
export function extractCodeBlocks(text: string) {
const blocks:Array<CodeBlock> = [];
for (const match of text.matchAll(regex)) {
const { content, type } = match.groups! as {
content: string;
type: string;
};
const start = match.index!;
const end = start + match[0].length;
const codeStart = start + 3 + type.length + 1;
const codeEnd = codeStart + content.length;
const [codeType, id] = type.split(':');
blocks.push({
blockRange: [start, end],
codeRange: [codeStart, codeEnd],
codeType,
content,
id,
});
}
return blocks;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment