// VSCode Extension, 23.06.24.18.58 // A VSCode extension for live coding aesthetic.computer pieces and // exploring the system documentation. /* #region TODO ๐Ÿ““ #endregion */ // Import necessary modules from vscode import * as vscode from "vscode"; import { Buffer } from "buffer"; // (generated-views import removed โ€” welcome panel now loads dev.html via iframe only) // ๐ŸŒˆ KidLisp Syntax Highlighting // Embedded copy of shared/kidlisp-syntax.mjs for bundling // This provides Monaco-parity highlighting for .lisp files in VS Code import * as KidLispSyntax from "./kidlisp-syntax"; // Dynamically import path, fs, child_process, and http to ensure web compatibility. let path: any, fs: any, cp: any, http: any; let _modulesReady: Promise; _modulesReady = (async () => { if (typeof window === "undefined") { path = await import("path"); fs = await import("fs"); cp = await import("child_process"); http = await import("http"); } })(); import { AestheticAuthenticationProvider } from "./aestheticAuthenticationProviderRemote"; import * as acorn from "acorn"; const { keys } = Object; // AST parsing and tracking for JS/MJS files interface ASTNode { id: string; type: string; name?: string; start: number; end: number; children: ASTNode[]; parent?: string; depth: number; loc?: { start: { line: number; column: number }; end: { line: number; column: number } }; // Extra metadata for richer display kind?: string; // const, let, var for VariableDeclaration source?: string; // import source path callee?: string; // function being called property?: string; // member access property value?: string; // literal value } interface TrackedFile { uri: string; fileName: string; filePath: string; ast: ASTNode | null; lastUpdate: number; } const trackedFiles = new Map(); let astUpdateCallback: ((files: TrackedFile[]) => void) | null = null; function parseJSToAST(code: string, fileName: string): ASTNode | null { try { const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true, }); let nodeId = 0; function convertNode(node: any, depth: number = 0, parentId?: string): ASTNode { const id = `${fileName}-${nodeId++}`; const children: ASTNode[] = []; // Get a friendly name for the node let name: string | undefined; let kind: string | undefined; let source: string | undefined; let callee: string | undefined; let property: string | undefined; let value: string | undefined; // Extract identifier names if (node.id?.name) name = node.id.name; else if (node.key?.name) name = node.key.name; else if (node.key?.value) name = String(node.key.value); else if (node.name) name = node.name; else if (node.type === 'Identifier') name = node.name; // Extract additional metadata based on node type switch (node.type) { case 'VariableDeclaration': kind = node.kind; // const, let, var break; case 'ImportDeclaration': source = node.source?.value; break; case 'ExportNamedDeclaration': case 'ExportDefaultDeclaration': if (node.declaration?.id?.name) name = node.declaration.id.name; if (node.source?.value) source = node.source.value; break; case 'CallExpression': if (node.callee?.name) callee = node.callee.name; else if (node.callee?.property?.name) callee = node.callee.property.name; else if (node.callee?.type === 'MemberExpression') { const obj = node.callee.object?.name || ''; const prop = node.callee.property?.name || ''; callee = obj ? `${obj}.${prop}` : prop; } break; case 'MemberExpression': property = node.property?.name || node.property?.value; break; case 'Literal': value = String(node.value).slice(0, 30); name = value.length > 15 ? value.slice(0, 12) + 'โ€ฆ' : value; break; case 'TemplateLiteral': name = '`template`'; break; case 'Property': name = node.key?.name || node.key?.value; break; } const astNode: ASTNode = { id, type: node.type, name, start: node.start, end: node.end, children, parent: parentId, depth, loc: node.loc, kind, source, callee, property, value, }; // Recursively process child nodes for (const key of Object.keys(node)) { if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue; const val = node[key]; if (val && typeof val === 'object') { if (Array.isArray(val)) { for (const item of val) { if (item && typeof item === 'object' && item.type) { children.push(convertNode(item, depth + 1, id)); } } } else if (val.type) { children.push(convertNode(val, depth + 1, id)); } } } return astNode; } return convertNode(ast); } catch (e) { console.log(`AST parse error for ${fileName}:`, e); return null; } } function updateTrackedFile(document: vscode.TextDocument) { const uri = document.uri.toString(); const filePath = document.fileName; const fileName = filePath.split('/').pop() || filePath; // Track JS/MJS/TS files (for AST), plus Markdown and Lisp files (as open buffers) const isCodeFile = fileName.endsWith('.js') || fileName.endsWith('.mjs') || fileName.endsWith('.ts'); const isMarkdown = fileName.endsWith('.md'); const isLisp = fileName.endsWith('.lisp'); if (!isCodeFile && !isMarkdown && !isLisp) { return; } // Only parse AST for code files const ast = isCodeFile ? parseJSToAST(document.getText(), fileName) : null; trackedFiles.set(uri, { uri, fileName, filePath, ast, lastUpdate: Date.now(), }); if (astUpdateCallback) { astUpdateCallback(Array.from(trackedFiles.values())); } } function removeTrackedFile(uri: string) { trackedFiles.delete(uri); if (astUpdateCallback) { astUpdateCallback(Array.from(trackedFiles.values())); } } let local: boolean = false; let localServerAvailable: boolean = false; let codeChannel: string | undefined; // Detect if we're in GitHub Codespaces const isCodespaces = process?.env.CODESPACES === "true"; const codespaceName = process?.env.CODESPACE_NAME; const codespacesDomain = process?.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; let mergedDocs: any = {}; let docs: any; let extContext: any; let webWindow: any; let kidlispWindow: any; let newsWindow: any; let atWindow: any; let welcomePanel: vscode.WebviewPanel | null = null; let localServerCheckInterval: NodeJS.Timeout | undefined; let provider: AestheticViewProvider; let lastWebviewRefreshAt = 0; // Debounce webview refreshes const WEBVIEW_REFRESH_DEBOUNCE_MS = 10000; // Minimum 10s between auto-refreshes // Check if the local server is available async function checkLocalServer(): Promise { try { // Use https module directly to allow self-signed certificates const https = await import("https"); return new Promise((resolve) => { const req = https.request( { hostname: "localhost", port: 8888, // HEAD can return 404/redirect even when the server is healthy. // We only care that something is listening and responding. path: `/?vscode=1&ts=${Date.now()}`, method: "GET", rejectUnauthorized: false, // Allow self-signed certs timeout: 2000, }, (res) => { // Any response means the server is reachable. resolve(true); // Ensure we don't hang waiting for body data. res.resume(); } ); req.on("error", () => resolve(false)); req.on("timeout", () => { req.destroy(); resolve(false); }); req.end(); }); } catch (e) { return false; } } // Start polling for local server availability function startLocalServerCheck() { if (localServerCheckInterval) { clearInterval(localServerCheckInterval); } // Helper to refresh all webviews with debouncing function refreshAllWebviews() { const now = Date.now(); if (now - lastWebviewRefreshAt < WEBVIEW_REFRESH_DEBOUNCE_MS) { console.log("โณ Skipping webview refresh (debounced)"); return; } lastWebviewRefreshAt = now; console.log("โœ… Local server is now available"); // Refresh webviews when server becomes available if (provider) provider.refreshWebview(); refreshWebWindow(); refreshKidLispWindow(); refreshNewsWindow(); refreshAtWindow(); } // Check immediately checkLocalServer().then((available) => { const wasAvailable = localServerAvailable; localServerAvailable = available; if (available && !wasAvailable) { refreshAllWebviews(); } }); // Then check every 3 seconds localServerCheckInterval = setInterval(async () => { const wasAvailable = localServerAvailable; localServerAvailable = await checkLocalServer(); if (localServerAvailable && !wasAvailable) { refreshAllWebviews(); } else if (!localServerAvailable && wasAvailable) { console.log("โณ Local server disconnected - waiting for reconnect..."); // Don't immediately show waiting screen - server may come back quickly during hot reload // The webview will show the waiting screen on next manual refresh or when we first load } }, 3000); } // Stop polling for local server function stopLocalServerCheck() { if (localServerCheckInterval) { clearInterval(localServerCheckInterval); localServerCheckInterval = undefined; } } // ๐ŸŒˆ KidLisp Syntax Highlighting Setup // Uses atomic decoration updates to avoid flickering (like Monaco's deltaDecorations) const kidlispDecorationTypes = new Map(); let kidlispHighlightInterval: NodeJS.Timeout | undefined; // Track which decoration type keys were used in the last render per editor const lastUsedDecorationKeys = new Map>(); function getOrCreateDecorationType(color: string, options: { bold?: boolean } = {}): vscode.TextEditorDecorationType { const key = `${color}-${options.bold ? 'bold' : 'normal'}`; if (!kidlispDecorationTypes.has(key)) { const hexColor = KidLispSyntax.colorToHex(color); kidlispDecorationTypes.set(key, vscode.window.createTextEditorDecorationType({ color: hexColor, fontWeight: options.bold ? 'bold' : 'normal', })); } return kidlispDecorationTypes.get(key)!; } function applyKidLispDecorations(editor: vscode.TextEditor, isEditMode: boolean = true) { if (!editor || editor.document.languageId !== 'kidlisp') { return; } const document = editor.document; const text = document.getText(); const tokens = KidLispSyntax.tokenizeWithPositions(text); const tokenValues = tokens.map(t => t.value); // Group decorations by color const decorationsByColor = new Map(); // Check if we're in light mode const isLightTheme = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Light || vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.HighContrastLight; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; let color = KidLispSyntax.getTokenColor(token.value, tokenValues, i, { isEditMode }); // Handle special color markers if (color === 'RAINBOW') { // Rainbow: each character gets a different color for (let j = 0; j < token.value.length; j++) { const charColor = KidLispSyntax.RAINBOW_COLORS[j % KidLispSyntax.RAINBOW_COLORS.length]; const startPos = document.positionAt(token.pos + j); const endPos = document.positionAt(token.pos + j + 1); const range = new vscode.Range(startPos, endPos); if (!decorationsByColor.has(charColor)) { decorationsByColor.set(charColor, []); } decorationsByColor.get(charColor)!.push({ range }); } continue; } if (color === 'ZEBRA') { // Zebra: alternating black and white for (let j = 0; j < token.value.length; j++) { const charColor = KidLispSyntax.ZEBRA_COLORS[j % KidLispSyntax.ZEBRA_COLORS.length]; const startPos = document.positionAt(token.pos + j); const endPos = document.positionAt(token.pos + j + 1); const range = new vscode.Range(startPos, endPos); if (!decorationsByColor.has(charColor)) { decorationsByColor.set(charColor, []); } decorationsByColor.get(charColor)!.push({ range }); } continue; } if (color.startsWith('COMPOUND:')) { // Compound: prefix char in one color, rest in another const [, prefixColor, identifierColor] = color.split(':'); // Prefix character ($ or # or !) const prefixStart = document.positionAt(token.pos); const prefixEnd = document.positionAt(token.pos + 1); const prefixRange = new vscode.Range(prefixStart, prefixEnd); const finalPrefixColor = isLightTheme ? KidLispSyntax.getLightModeColor(prefixColor) : prefixColor; if (!decorationsByColor.has(finalPrefixColor)) { decorationsByColor.set(finalPrefixColor, []); } decorationsByColor.get(finalPrefixColor)!.push({ range: prefixRange }); // Identifier part const idStart = document.positionAt(token.pos + 1); const idEnd = document.positionAt(token.pos + token.value.length); const idRange = new vscode.Range(idStart, idEnd); const finalIdColor = isLightTheme ? KidLispSyntax.getLightModeColor(identifierColor) : identifierColor; if (!decorationsByColor.has(finalIdColor)) { decorationsByColor.set(finalIdColor, []); } decorationsByColor.get(finalIdColor)!.push({ range: idRange }); continue; } // Regular color const finalColor = isLightTheme ? KidLispSyntax.getLightModeColor(color) : color; const startPos = document.positionAt(token.pos); const endPos = document.positionAt(token.pos + token.value.length); const range = new vscode.Range(startPos, endPos); if (!decorationsByColor.has(finalColor)) { decorationsByColor.set(finalColor, []); } decorationsByColor.get(finalColor)!.push({ range }); } // Atomic update: apply new decorations and clear stale ones in one pass // setDecorations(type, ranges) replaces all decorations for that type, // so we don't need to clear first โ€” just set the new ranges. const editorId = editor.document.uri.toString(); const currentKeys = new Set(); for (const [color, ranges] of decorationsByColor) { const decorationType = getOrCreateDecorationType(color, { bold: true }); const key = `${color}-bold`; currentKeys.add(key); editor.setDecorations(decorationType, ranges); } // Only clear decoration types that were used last time but NOT this time const previousKeys = lastUsedDecorationKeys.get(editorId); if (previousKeys) { for (const key of previousKeys) { if (!currentKeys.has(key)) { const decorationType = kidlispDecorationTypes.get(key); if (decorationType) { editor.setDecorations(decorationType, []); } } } } lastUsedDecorationKeys.set(editorId, currentKeys); } // Check if any visible KidLisp editor has timing tokens that need blinking const timingPattern = /\d*\.?\d+[sf](?:\.\.\.?|!?)/; function hasTimingTokens(): boolean { for (const editor of vscode.window.visibleTextEditors) { if (editor.document.languageId === 'kidlisp' && timingPattern.test(editor.document.getText())) { return true; } } return false; } function setupKidLispHighlighting(context: vscode.ExtensionContext) { // Apply highlighting to currently visible KidLisp editors function refreshAllKidLispEditors() { for (const editor of vscode.window.visibleTextEditors) { if (editor.document.languageId === 'kidlisp') { applyKidLispDecorations(editor, true); } } } // Initial application refreshAllKidLispEditors(); // Refresh when editors change context.subscriptions.push( vscode.window.onDidChangeVisibleTextEditors(() => { refreshAllKidLispEditors(); }) ); // Refresh when document content changes context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(event => { const editor = vscode.window.visibleTextEditors.find( e => e.document === event.document ); if (editor && editor.document.languageId === 'kidlisp') { applyKidLispDecorations(editor, true); } }) ); // Refresh when theme changes (for light/dark mode support) context.subscriptions.push( vscode.window.onDidChangeActiveColorTheme(() => { // Clear cached decoration types since colors may need updating for (const [, decorationType] of kidlispDecorationTypes) { decorationType.dispose(); } kidlispDecorationTypes.clear(); lastUsedDecorationKeys.clear(); refreshAllKidLispEditors(); }) ); // Timing blink animation interval โ€” only refreshes when timing tokens exist if (kidlispHighlightInterval) { clearInterval(kidlispHighlightInterval); } kidlispHighlightInterval = setInterval(() => { if (hasTimingTokens()) { refreshAllKidLispEditors(); } }, 250); // ~4fps is enough for blink animation // Clean up on deactivation context.subscriptions.push({ dispose: () => { if (kidlispHighlightInterval) { clearInterval(kidlispHighlightInterval); kidlispHighlightInterval = undefined; } for (const [, decorationType] of kidlispDecorationTypes) { decorationType.dispose(); } kidlispDecorationTypes.clear(); lastUsedDecorationKeys.clear(); } }); } async function activate(context: vscode.ExtensionContext): Promise { // local = context.globalState.get("aesthetic:local", false); // Retrieve env. // Show all environment variables... // console.log("๐ŸŒŽ Environment:", process.env); // Always retrieve the stored local state first local = context.globalState.get("aesthetic:local", false); const isInDevContainer = process?.env.REMOTE_CONTAINERS === "true"; if (isInDevContainer) { // console.log("โœ… ๐Ÿฅก Running inside a container."); // Keep the retrieved local state } else { // console.log("โŒ ๐Ÿฅก Not in container."); // Keep the user's preference even when not in container } // Start checking for local server if local mode is enabled if (local) { startLocalServerCheck(); } // ๐ŸŒณ AST Tracking: Watch open JS/MJS documents for live AST visualization // Track currently open editors vscode.window.visibleTextEditors.forEach(editor => { updateTrackedFile(editor.document); }); // Track when editors change context.subscriptions.push( vscode.window.onDidChangeVisibleTextEditors(editors => { // Add new files editors.forEach(editor => updateTrackedFile(editor.document)); }) ); // Track document changes for live updates context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(event => { if (trackedFiles.has(event.document.uri.toString())) { updateTrackedFile(event.document); } }) ); // Track document closes context.subscriptions.push( vscode.workspace.onDidCloseTextDocument(document => { removeTrackedFile(document.uri.toString()); }) ); // ๐Ÿ›ก๏ธ Periodic Cleanup: Ensure tracked files match actual open documents // (Fixes issues where files might get "stuck" if events are missed) setInterval(() => { const openUris = new Set(vscode.workspace.textDocuments.map(d => d.uri.toString())); let changed = false; for (const [uri, file] of trackedFiles) { if (!openUris.has(uri)) { console.log('๐Ÿงน Pruning stuck file:', file.fileName); trackedFiles.delete(uri); changed = true; } } if (changed && astUpdateCallback) { astUpdateCallback(Array.from(trackedFiles.values())); } }, 2000); // Set up AST update callback to send to welcome panel astUpdateCallback = (files) => { if (welcomePanel) { welcomePanel.webview.postMessage({ command: 'astUpdate', files: files.map(f => ({ id: f.uri, // Use URI as unique ID fileName: f.fileName, filePath: f.filePath, // Send full path ast: f.ast, lastUpdate: f.lastUpdate, })), }); } }; // ๐ŸŒˆ KidLisp Syntax Highlighting with Decorations // This provides Monaco-parity highlighting for .lisp files including: // - Rainbow/zebra color words, timing blinks, fade expressions // - Color codes, CSS colors, RGB channels, nested paren colors setupKidLispHighlighting(context); // console.log("๐ŸŸข Aesthetic Computer Extension: Activated"); extContext = context; // Load the docs from the web. if (!docs) { try { // const url = `https://${ // local ? "localhost:8888" : "aesthetic.computer" }/api/docs`; const url = `https://aesthetic.computer/docs.json`; const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: any = await response.json(); // console.log("๐Ÿ“š Docs loaded:", data); keys(data.api).forEach((key) => { // Add the category to each doc before smushing them. // Such as `structure` so doc pages can be found like `structure:boot`. keys(data.api[key]).forEach((k) => { data.api[key][k].category = key; }); // ๐Ÿ˜› Smush them. mergedDocs = { ...mergedDocs, ...data.api[key], }; }); docs = data; } catch (error) { console.error("Failed to fetch documentation:", error); } } // Set up all the autocompletion and doc hints. const codeLensProvider = new AestheticCodeLensProvider(); vscode.languages.registerCodeLensProvider( { language: "javascript", pattern: "**/*.mjs" }, codeLensProvider, ); const completionProvider = vscode.languages.registerCompletionItemProvider( { language: "javascript", pattern: "**/*.mjs" }, { provideCompletionItems( document: vscode.TextDocument, position: vscode.Position, ): vscode.ProviderResult< vscode.CompletionItem[] | vscode.CompletionList > { if (document.lineCount > 500 || !docs) return []; // Skip for long files. return keys(mergedDocs).map( (word: string) => new vscode.CompletionItem(word), ); }, }, ); context.subscriptions.push(completionProvider); const hoverProvider = vscode.languages.registerHoverProvider( { language: "javascript", pattern: "**/*.mjs" }, { provideHover(document, position) { if (document.lineCount > 500 || !docs) return; // Skip for long files. const range = document.getWordRangeAtPosition(position); const word = document.getText(range); if (keys(mergedDocs).indexOf(word) > -1) { const contents = new vscode.MarkdownString(); contents.isTrusted = true; // Enable for custom markdown. contents.appendCodeblock(`${mergedDocs[word].sig}`, "javascript"); contents.appendText("\n\n"); contents.appendMarkdown(`${mergedDocs[word].desc}`); return new vscode.Hover(contents, range); } }, }, ); context.subscriptions.push(hoverProvider); // Code Action Provider for manual documentation opening const codeActionProvider = vscode.languages.registerCodeActionsProvider( { language: "javascript", pattern: "**/*.mjs" }, { provideCodeActions( document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { if (document.lineCount > 500 || !docs) return []; // Skip for long files. const wordRange = document.getWordRangeAtPosition(range.start); if (!wordRange) return []; const word = document.getText(wordRange); if (keys(mergedDocs).indexOf(word) > -1) { const action = new vscode.CodeAction( `๐Ÿ“š Open documentation for "${word}"`, vscode.CodeActionKind.QuickFix ); action.command = { title: `Open docs for ${word}`, command: "aestheticComputer.openDoc", arguments: [word] }; action.isPreferred = true; return [action]; } return []; } } ); context.subscriptions.push(codeActionProvider); const definitionProvider = vscode.languages.registerDefinitionProvider( { language: "javascript", pattern: "**/*.mjs" }, { provideDefinition( document, position, token, ): vscode.ProviderResult { if (document.lineCount > 500) return; // Skip for large documents. const range = document.getWordRangeAtPosition(position); const word = document.getText(range); if (mergedDocs[word]) { // Instead of automatically opening docs, create a virtual definition location // that shows in the peek definition view without triggering a popup const uri = vscode.Uri.parse(`aesthetic-doc:${word}`); return new vscode.Location(uri, new vscode.Position(0, 0)); } return null; }, }, ); let docsPanel: any = null; // Only keep one docs panel open. context.subscriptions.push( vscode.commands.registerCommand( "aestheticComputer.openDoc", (functionName) => { // const uri = vscode.Uri.parse(`${docScheme}:${functionName}`); let path = ""; if (functionName) path = "/" + mergedDocs[functionName].category + ":" + functionName; const title = path || "docs"; // Check if the panel already exists. If so, reveal it. if (docsPanel) { // Update the title docsPanel.title = "๐Ÿ“š " + title.replace("/", "") + " ยท Aesthetic Computer"; docsPanel.reveal(vscode.ViewColumn.Beside); } else { // Create and show a new webview docsPanel = vscode.window.createWebviewPanel( "aestheticDoc", // Identifies the type of the webview. Used internally "๐Ÿ“š " + title.replace("/", "") + " ยท Aesthetic Computer", // Title of the panel displayed to the user vscode.ViewColumn.Beside, // Editor column to show the new webview panel in. { enableScripts: true, enableForms: true }, // Webview options. ); // Reset when the current panel is closed docsPanel.onDidDispose(() => { docsPanel = null; }, null); } const nonce = getNonce(); // Build CSP frame-src based on environment let cspFrameSrc = "frame-src https://aesthetic.computer https://aesthetic.local:8888 https://localhost:8888"; let cspChildSrc = "child-src https://aesthetic.computer https://aesthetic.local:8888 https://localhost:8888"; if (isCodespaces && codespacesDomain) { const codespaceWildcard = `https://*.${codespacesDomain}`; cspFrameSrc += ` ${codespaceWildcard}`; cspChildSrc += ` ${codespaceWildcard}`; } // And set its HTML content docsPanel.webview.html = ` `; } // ๐Ÿ“ฐ News WebView Content function getNewsWebViewContent(webview: any) { const nonce = getNonce(); // Detect VS Code theme for News waiting screen const themeKind = vscode.window.activeColorTheme.kind; const isDark = themeKind === 2 || themeKind === 3; // Dark or HighContrast const styleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "main.css"), ); const resetStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "reset.css"), ); const vscodeStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "vscode.css"), ); if (local && !localServerAvailable && !isCodespaces) { // Theme-aware colors for waiting screen const bg = isDark ? '#1a1a1a' : '#f7f1e1'; const textColor = isDark ? '#d4d4d4' : '#3b2a1a'; const titleColor = isDark ? '#ff69b4' : '#a85a2a'; const subtitleBg = isDark ? '#252525' : '#fff9f0'; const subtitleBorder = isDark ? '#404040' : '#e2d4c3'; const subtitleColor = isDark ? '#a0a0a0' : '#6b4a2e'; const codeColor = isDark ? '#ff69b4' : '#a85a2a'; return ` News
๐Ÿ“ฐ
Waiting for local server
Run ac-site to start localhost:8888
`; } let iframeUrl; let iframeProtocol = "https://"; let path = ""; if (isCodespaces && codespaceName && codespacesDomain) { iframeUrl = `${codespaceName}-8888.${codespacesDomain}`; path = "/news.aesthetic.computer"; } else if (local) { iframeUrl = "localhost:8888"; path = "/news.aesthetic.computer"; } else { iframeUrl = "news.aesthetic.computer"; path = ""; } let cspFrameSrc = "frame-src https://news.aesthetic.computer https://localhost:8888 https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com"; let cspChildSrc = "child-src https://news.aesthetic.computer https://localhost:8888 https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com"; if (isCodespaces && codespacesDomain) { const codespaceWildcard = `https://*.${codespacesDomain}`; cspFrameSrc += ` ${codespaceWildcard}`; cspChildSrc += ` ${codespaceWildcard}`; } const sessionAesthetic = extContext.globalState.get( "aesthetic:session", undefined, ); let param = "?vscode=true"; if (sessionAesthetic && typeof sessionAesthetic === "object") { try { const encoded = Buffer.from(JSON.stringify(sessionAesthetic)).toString( "base64", ); param += `&session-aesthetic=${encodeURIComponent(encoded)}`; } catch (e) { console.log("๐Ÿ”ด Failed to encode session for News webview:", e); param += "&session-aesthetic=null"; } } else { param += "&session-aesthetic=null"; } return ` News `; } // ๐Ÿงญ AT WebView Content function getAtWebViewContent(webview: any) { const nonce = getNonce(); const styleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "main.css"), ); const resetStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "reset.css"), ); const vscodeStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "vscode.css"), ); let iframeUrl; let iframeProtocol = "https://"; if (isCodespaces && codespaceName && codespacesDomain) { iframeUrl = `${codespaceName}-4177.${codespacesDomain}`; } else if (local) { iframeUrl = "localhost:4177"; iframeProtocol = "http://"; } else { iframeUrl = "at.aesthetic.computer"; } let cspFrameSrc = "frame-src https://at.aesthetic.computer http://localhost:4177"; let cspChildSrc = "child-src https://at.aesthetic.computer http://localhost:4177"; if (isCodespaces && codespacesDomain) { const codespaceWildcard = `https://*.${codespacesDomain}`; cspFrameSrc += ` ${codespaceWildcard}`; cspChildSrc += ` ${codespaceWildcard}`; } const path = local ? "/user.html" : "/"; const param = local ? "?handle=art.at.aesthetic.computer&vscode=true" : "?vscode=true"; return ` AT `; } // ๐ŸŒˆ KidLisp.com WebView Content function getKidLispWebViewContent(webview: any) { const nonce = getNonce(); // Detect VS Code theme for KidLisp waiting screen const themeKind = vscode.window.activeColorTheme.kind; const isDark = themeKind === 2 || themeKind === 3; // Dark or HighContrast const styleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "main.css"), ); const resetStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "reset.css"), ); const vscodeStyleUri = webview.asWebviewUri( vscode.Uri.joinPath(extContext.extensionUri, "vscode.css"), ); // Show waiting UI if local mode is enabled but server isn't available yet if (local && !localServerAvailable && !isCodespaces) { // Theme-aware colors const bg = isDark ? 'linear-gradient(135deg, #181318 0%, #141214 100%)' : 'linear-gradient(135deg, #fffacd 0%, #fff9c0 100%)'; const textColor = isDark ? '#ffffffcc' : '#333'; const accentColor = isDark ? '#ff69b4' : '#9370DB'; const subtitleBg = isDark ? '#1a1a1a' : '#fff'; const subtitleBorder = isDark ? '#483848' : '#e0d8a8'; const subtitleColor = isDark ? '#b0a0a8' : '#666'; return ` KidLisp.com
๐ŸŒˆ
Waiting for local server
Run ac-site to start localhost:8888
`; } // Determine the iframe URL based on environment let iframeUrl; let iframeProtocol = "https://"; if (isCodespaces && codespaceName && codespacesDomain) { iframeUrl = `${codespaceName}-8888.${codespacesDomain}`; } else { iframeUrl = local ? "localhost:8888" : "aesthetic.computer"; } // Build CSP for kidlisp.com let cspFrameSrc = "frame-src https://aesthetic.computer https://localhost:8888"; let cspChildSrc = "child-src https://aesthetic.computer https://localhost:8888"; if (isCodespaces && codespacesDomain) { const codespaceWildcard = `https://*.${codespacesDomain}`; cspFrameSrc += ` ${codespaceWildcard}`; cspChildSrc += ` ${codespaceWildcard}`; } // Encode current session for kidlisp.com so it can hydrate without another login const sessionAesthetic = extContext.globalState.get( "aesthetic:session", undefined, ); let param = "?vscode=true"; if (sessionAesthetic && typeof sessionAesthetic === "object") { try { const encoded = Buffer.from(JSON.stringify(sessionAesthetic)).toString( "base64", ); param += `&session-aesthetic=${encodeURIComponent(encoded)}`; } catch (e) { console.log("๐Ÿ”ด Failed to encode session for KidLisp webview:", e); param += "&session-aesthetic=null"; } } else { param += "&session-aesthetic=null"; } return ` KidLisp.com `; } export { activate, AestheticViewProvider };