Fix Monaco editor syntax highlighting in Preview block (#2459)

The Preview block's CodeEditPreview component was not passing file
information to Monaco editor, preventing automatic language detection
from file extensions.

Changes:
- Add fileName prop to CodeEditor component
- Pass actual file path to Monaco with fallback to random UUID

Fixes syntax highlighting for all text files opened in Preview blocks
while maintaining backward compatibility with existing CodeEditor usage.

---------

Co-authored-by: sawka
This commit is contained in:
Matt 2025-10-20 19:26:47 -05:00 committed by GitHub
parent ae42e2ccce
commit 6bfb9e650a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 6 deletions

View file

@ -1,7 +1,7 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { useCallback, useLayoutEffect, useState } from "react";
import { useCallback, useLayoutEffect, useRef, useState } from "react";
import { FakeBlock } from "./onboarding-layout";
import waveLogo from "/logos/wave-logo.png";
@ -114,6 +114,7 @@ export const ViewLogoCommand = ({ onComplete }: { onComplete?: () => void }) =>
};
export const EditBashrcCommand = ({ onComplete }: { onComplete?: () => void }) => {
const fileNameRef = useRef(`${crypto.randomUUID()}/.bashrc`);
const bashrcContent = `# Aliases
alias ll="ls -lah"
alias gst="git status"
@ -127,7 +128,13 @@ export PATH="$HOME/.local/bin:$PATH"`;
return (
<FakeCommand command="wsh edit ~/.bashrc" onComplete={onComplete}>
<FakeBlock icon="file-lines" name=".bashrc" editorText={bashrcContent} />
<FakeBlock
icon="file-lines"
name=".bashrc"
editorText={bashrcContent}
editorFileName={fileNameRef.current}
editorLanguage="shell"
/>
</FakeCommand>
);
};

View file

@ -15,9 +15,21 @@ export type FakeBlockProps = {
markdown?: string;
imgsrc?: string;
editorText?: string;
editorFileName?: string;
editorLanguage?: string;
};
export const FakeBlock = ({ icon, name, highlighted, className, markdown, imgsrc, editorText }: FakeBlockProps) => {
export const FakeBlock = ({
icon,
name,
highlighted,
className,
markdown,
imgsrc,
editorText,
editorFileName,
editorLanguage,
}: FakeBlockProps) => {
return (
<div
className={cn(
@ -37,7 +49,13 @@ export const FakeBlock = ({ icon, name, highlighted, className, markdown, imgsrc
<div className="flex-1 flex items-center justify-center overflow-auto p-4">
{editorText ? (
<div className="w-full h-full">
<CodeEditor blockId="fake-block" text={editorText} readonly={true} language="shell" />
<CodeEditor
blockId="fake-block"
text={editorText}
readonly={true}
fileName={editorFileName}
language={editorLanguage ?? "shell"}
/>
</div>
) : imgsrc ? (
<img src={imgsrc} alt={name} className="max-w-full max-h-full object-contain" />

View file

@ -108,11 +108,12 @@ interface CodeEditorProps {
text: string;
readonly: boolean;
language?: string;
fileName?: string;
onChange?: (text: string) => void;
onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => () => void;
}
export function CodeEditor({ blockId, text, language, readonly, onChange, onMount }: CodeEditorProps) {
export function CodeEditor({ blockId, text, language, fileName, readonly, onChange, onMount }: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const unmountRef = useRef<() => void>(null);
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
@ -120,7 +121,14 @@ export function CodeEditor({ blockId, text, language, readonly, onChange, onMoun
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64);
const theme = "wave-theme-dark";
const editorPath = useRef(crypto.randomUUID()).current;
const uuidRef = useRef(crypto.randomUUID()).current;
let editorPath: string;
if (fileName) {
const separator = fileName.startsWith("/") ? "" : "/";
editorPath = blockId + separator + fileName;
} else {
editorPath = uuidRef;
}
React.useEffect(() => {
return () => {

View file

@ -12,10 +12,35 @@ import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import { useEffect } from "react";
import type { SpecializedViewProps } from "./preview";
export const shellFileMap: Record<string, string> = {
".bashrc": "shell",
".bash_profile": "shell",
".bash_login": "shell",
".bash_logout": "shell",
".profile": "shell",
".zshrc": "shell",
".zprofile": "shell",
".zlogin": "shell",
".zlogout": "shell",
".kshrc": "shell",
".cshrc": "shell",
".tcshrc": "shell",
".xonshrc": "python",
".shrc": "shell",
".aliases": "shell",
".functions": "shell",
".exports": "shell",
".direnvrc": "shell",
};
function CodeEditPreview({ model }: SpecializedViewProps) {
const fileContent = useAtomValue(model.fileContent);
const setNewFileContent = useSetAtom(model.newFileContent);
const fileInfo = useAtomValue(model.statFile);
const fileName = fileInfo?.path || fileInfo?.name;
const baseName = fileName ? fileName.split("/").pop() : null;
const language = baseName && shellFileMap[baseName] ? shellFileMap[baseName] : undefined;
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
if (checkKeyPressed(e, "Cmd:e")) {
@ -65,6 +90,8 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
<CodeEditor
blockId={model.blockId}
text={fileContent}
fileName={fileName}
language={language}
readonly={fileInfo.readonly}
onChange={(text) => setNewFileContent(text)}
onMount={onMount}