feat: enhance VSCode API compatibility with fallback state management

This commit is contained in:
Vadim Melnicuk
2026-03-24 20:31:42 +00:00
parent f5f20a7245
commit da2ac232ff
3 changed files with 75 additions and 6 deletions
+26 -3
View File
@@ -707,6 +707,8 @@ export function addTopLinePillLabel(builder: any[], lineEnd: number, labelText:
}
const quotedFenceOpeningLineRegex = /^[ \t]{0,3}(?:>[ \t]?)*[ \t]{0,3}(?:`{3,}|~{3,})/;
const fenceLineRegex = /^[ \t]*[`~]{3,}.*$/;
const quotedFencePrefixRegex = /^[ \t]{0,3}((?:>[ \t]?)*)[ \t]{0,3}(?:`{3,}|~{3,})/;
export function addFenceOpeningLineMarker(builder: any[], state: EditorState, from: number, activeLines: Set<number>, addRange: Function, activeLineMarkerDeco: any, fenceMarkerDeco: any): void {
const line = state.doc.lineAt(from);
@@ -797,15 +799,15 @@ export function addMermaidDiagramBlock(
export function addCopyCodeButton(builder: any[], state: EditorState, from: number, to: number): void {
const startLine = state.doc.lineAt(from);
const endLine = state.doc.lineAt(Math.max(to - 1, from));
const quoteDepth = getQuotedFenceDepth(startLine.text);
const codeLines: string[] = [];
for (let lineNum = startLine.number + 1; lineNum <= endLine.number; lineNum += 1) {
const line = state.doc.line(lineNum);
const lineText = line.text;
const lineText = stripLeadingQuotePrefix(line.text, quoteDepth);
if (lineNum === endLine.number) {
const fenceMatch = /^[ \t]*[`~]{3,}.*$/.exec(lineText);
if (fenceMatch) {
if (fenceLineRegex.test(lineText)) {
continue;
}
}
@@ -820,3 +822,24 @@ export function addCopyCodeButton(builder: any[], state: EditorState, from: numb
addTopLineCopyButton(builder, startLine.to, codeContent);
}
function getQuotedFenceDepth(lineText: string): number {
const match = quotedFencePrefixRegex.exec(lineText);
if (!match) {
return 0;
}
return (match[1].match(/>/g) ?? []).length;
}
function stripLeadingQuotePrefix(lineText: string, quoteDepth: number): string {
if (quoteDepth <= 0) {
return lineText;
}
const prefixRe = new RegExp(`^[ \\t]{0,3}(?:>[ \\t]?){${quoteDepth}}`);
const match = prefixRe.exec(lineText);
if (!match) {
return lineText;
}
return lineText.slice(match[0].length);
}
+47 -1
View File
@@ -14,7 +14,53 @@ import { createExportHandler, type ExportHandlerContext } from './helpers/export
type CreateEditorFactory = (typeof import('./editor'))['createEditor'];
const vscode = acquireVsCodeApi();
type CompatibleVsCodeWebviewApi = {
postMessage: (message: WebviewMessage) => void;
getState: () => unknown;
setState: (state: unknown) => void;
};
function createCompatibleVsCodeApi(): CompatibleVsCodeWebviewApi {
const hostApi = acquireVsCodeApi();
let fallbackState: unknown;
const getState = (): unknown => {
if (typeof hostApi.getState !== 'function') {
return fallbackState;
}
try {
const hostState = hostApi.getState();
if (hostState !== undefined) {
fallbackState = hostState;
}
return hostState;
} catch {
return fallbackState;
}
};
const setState = (state: unknown): void => {
fallbackState = state;
if (typeof hostApi.setState !== 'function') {
return;
}
try {
hostApi.setState(state);
} catch {
// Keep session-local fallback state when host persistence is unavailable.
}
};
return {
postMessage: (message: WebviewMessage) => hostApi.postMessage(message),
getState,
setState
};
}
const vscode = createCompatibleVsCodeApi();
initializeImageHandling(vscode);
initializeWikiLinkHandling(vscode);
initializeLocalLinkHandling(vscode);
+2 -2
View File
@@ -1,8 +1,8 @@
declare function acquireVsCodeApi(): VsCodeWebviewApi;
interface VsCodeWebviewApi {
getState(): unknown;
setState(state: unknown): void;
getState?(): unknown;
setState?(state: unknown): void;
postMessage(message: WebviewMessage): void;
}