Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/runtime-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Runtime Tests CI
on:
pull_request:
paths:
- 'packages/runtime/**'
- '.github/workflows/runtime-tests.yml'
jobs:
test-runtime:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test -w @my-code/runtime
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ next-env.d.ts

/public/typescript/
/app/m-plus-rounded-1c-nohint/
node_modules
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ WORKDIR /app

COPY package.json package-lock.json ./
COPY packages/jsEval/package.json ./packages/jsEval/
COPY packages/runtime/package.json ./packages/runtime/

RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ Cloudflare Worker のビルドログとステータス表示が見れますが

## markdown仕様

実行環境の説明は ./packages/runtime/README.md を参照

````
```言語名-repl
>>> コマンド
Expand All @@ -162,9 +164,9 @@ Cloudflare Worker のビルドログとステータス表示が見れますが
````

でターミナルを埋め込む。
* ターミナル表示部は app/terminal/terminal.tsx
* コマンド入力処理は app/terminal/repl.tsx
* 各言語の実行環境は app/terminal/言語名/ 内に書く。
* ターミナル表示部は ./packages/runtime/terminal.tsx
* コマンド入力処理は ./packages/runtime/repl.tsx
* 各言語の実行環境は ./packages/runtime/言語名/ 内に書く。
* 実行結果はSectionContextにも送られ、section.tsxからアクセスできる

````
Expand All @@ -174,10 +176,10 @@ Cloudflare Worker のビルドログとステータス表示が見れますが
````

でテキストエディターを埋め込む。
* app/terminal/editor.tsx
* ./packages/runtime/editor.tsx
* editor.tsx内で `import "ace-builds/src-min-noconflict/mode-言語名";` を追加すればその言語に対応した色付けがされる。
* importできる言語の一覧は https://github.com/ajaxorg/ace-builds/tree/master/src-noconflict
* 編集した内容は app/terminal/file.tsx のFileContextで管理される。
* 編集した内容は ./packages/runtime/file.tsx のFileContextで管理される。
* 編集中のコードはFileContextに即時送られる
* FileContextが書き換えられたら即時すべてのエディターに反映される
* 編集したファイルの一覧はSectionContextにも送られ、section.tsxからアクセスできる
Expand All @@ -198,7 +200,7 @@ Cloudflare Worker のビルドログとステータス表示が見れますが

で実行ボタンを表示する
* 実行ボタンを押した際にFileContextからファイルを読み、実行し、結果を表示する
* app/terminal/exec.tsx に各言語ごとの実装を書く (それぞれ app/terminal/言語名/ 内に定義した関数を呼び出す)
* ./packages/runtime/exec.tsx に各言語ごとの実装を書く (それぞれ ./packages/runtime/言語名/ 内に定義した関数を呼び出す)
* 実行結果はSectionContextにも送られ、section.tsxからアクセスできる


Expand Down
32 changes: 12 additions & 20 deletions app/[lang]/[pageId]/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ import Markdown, { Components, ExtraProps } from "react-markdown";
import remarkGfm from "remark-gfm";
import removeComments from "remark-remove-comments";
import remarkCjkFriendly from "remark-cjk-friendly";
import { EditorComponent, getAceLang } from "@/terminal/editor";
import { EditorComponent } from "@/terminal/editor";
import { ExecFile } from "@/terminal/exec";
import { JSX, ReactNode } from "react";
import { getRuntimeLang } from "@/terminal/runtime";
import { langConstants, MarkdownLang } from "@my-code/runtime/languages";
import { ReplTerminal } from "@/terminal/repl";
import {
getSyntaxHighlighterLang,
MarkdownLang,
StyledSyntaxHighlighter,
} from "./styledSyntaxHighlighter";
import { StyledSyntaxHighlighter } from "./styledSyntaxHighlighter";

export function StyledMarkdown({ content }: { content: string }) {
return (
Expand Down Expand Up @@ -93,7 +89,7 @@ function CodeComponent({
className || ""
);
if (match) {
const runtimeLang = getRuntimeLang(match[1] as MarkdownLang | undefined);
const language = langConstants(match[1] as MarkdownLang | undefined);
if (match[2] === "-exec" && match[3]) {
/*
```python-exec:main.py
Expand All @@ -105,10 +101,10 @@ function CodeComponent({
hello, world!
---------------------------
*/
if (runtimeLang) {
if (language.runtime) {
return (
<ExecFile
language={runtimeLang}
language={language}
filenames={match[3].split(",")}
content={String(props.children || "").replace(/\n$/, "")}
/>
Expand All @@ -121,39 +117,35 @@ function CodeComponent({
`${match[1]}-repl without terminal id! content: ${String(props.children).slice(0, 20)}...`
);
}
if (runtimeLang) {
if (language.runtime) {
return (
<ReplTerminal
terminalId={match[3]}
language={runtimeLang}
language={language}
initContent={String(props.children || "").replace(/\n$/, "")}
/>
);
}
} else if (match[3]) {
// ファイル名指定がある場合、ファイルエディター
const aceLang = getAceLang(match[1] as MarkdownLang | undefined);
return (
<EditorComponent
language={aceLang}
language={language}
filename={match[3]}
readonly={match[2] === "-readonly"}
initContent={String(props.children || "").replace(/\n$/, "")}
/>
);
}
const syntaxHighlighterLang = getSyntaxHighlighterLang(
match[1] as MarkdownLang | undefined
);
return (
<StyledSyntaxHighlighter language={syntaxHighlighterLang}>
<StyledSyntaxHighlighter language={language}>
{String(props.children || "").replace(/\n$/, "")}
</StyledSyntaxHighlighter>
);
} else if (String(props.children).includes("\n")) {
// 言語指定なしコードブロック
return (
<StyledSyntaxHighlighter language={undefined}>
<StyledSyntaxHighlighter language={langConstants(undefined)}>
{String(props.children || "").replace(/\n$/, "")}
</StyledSyntaxHighlighter>
);
Expand All @@ -171,4 +163,4 @@ export function InlineCode({ children }: { children: ReactNode }) {
{children}
</code>
);
}
}
98 changes: 3 additions & 95 deletions app/[lang]/[pageId]/styledSyntaxHighlighter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
tomorrowNight,
} from "react-syntax-highlighter/dist/esm/styles/hljs";
import { lazy, Suspense, useEffect, useState } from "react";
import { LangConstants } from "@my-code/runtime/languages";

// SyntaxHighlighterはファイルサイズがでかいので & HydrationErrorを起こすので、SSRを無効化する
const SyntaxHighlighter = lazy(() => {
Expand All @@ -16,102 +17,9 @@ const SyntaxHighlighter = lazy(() => {
}
});

// Markdownで指定される可能性のある言語名を列挙
export type MarkdownLang =
| "python"
| "py"
| "ruby"
| "rb"
| "cpp"
| "c++"
| "rust"
| "rs"
| "javascript"
| "js"
| "typescript"
| "ts"
| "bash"
| "sh"
| "powershell"
| "json"
| "toml"
| "csv"
| "html"
| "makefile"
| "cmake"
| "text"
| "txt";

// react-syntax-highliter (hljs版) が対応している言語
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD を参照
export type SyntaxHighlighterLang =
| "python"
| "ruby"
| "c"
| "cpp"
| "rust"
| "javascript"
| "typescript"
| "bash"
| "powershell"
| "html"
| "json"
| "ini"
| "makefile"
| "cmake";
export function getSyntaxHighlighterLang(
lang: MarkdownLang | undefined
): SyntaxHighlighterLang | undefined {
switch (lang) {
case "python":
case "py":
return "python";
case "ruby":
case "rb":
return "ruby";
case "cpp":
case "c++":
return "cpp";
case "rust":
case "rs":
return "rust";
case "javascript":
case "js":
return "javascript";
case "typescript":
case "ts":
return "typescript";
case "bash":
case "sh":
return "bash";
case "powershell":
return "powershell";
case "json":
return "json";
case "toml":
return "ini";
case "html":
return "html";
case "makefile":
return "makefile";
case "cmake":
return "cmake";
case "csv": // not supported
case "text":
case "txt":
case undefined:
return undefined;
default:
lang satisfies never;
console.error(
`getSyntaxHighlighterLang() does not handle language ${lang}`
);
return undefined;
}
}
export function StyledSyntaxHighlighter(props: {
children: string;
language: SyntaxHighlighterLang | undefined;
language: LangConstants;
}) {
const theme = useChangeTheme();
const codetheme = theme === "tomorrow" ? tomorrow : tomorrowNight;
Expand All @@ -122,7 +30,7 @@ export function StyledSyntaxHighlighter(props: {
return initHighlighter ? (
<Suspense fallback={<FallbackPre>{props.children}</FallbackPre>}>
<SyntaxHighlighter
language={props.language}
language={props.language.rsh}
PreTag="div"
className="border-2 border-current/20 mx-2 my-2 rounded-box p-4! bg-base-300! text-base-content!"
style={codetheme}
Expand Down
2 changes: 1 addition & 1 deletion app/actions/chatActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// import { z } from "zod";
import { generateContent } from "./gemini";
import { DynamicMarkdownSection } from "../[lang]/[pageId]/pageContent";
import { ReplCommand, ReplOutput } from "../terminal/repl";
import { ReplCommand, ReplOutput } from "@my-code/runtime/interface";
import { addChat, ChatWithMessages } from "@/lib/chatHistory";

type ChatResult =
Expand Down
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import "./globals.css";
import { Navbar } from "./navbar";
import { Sidebar } from "./sidebar";
import { ReactNode } from "react";
import { EmbedContextProvider } from "./terminal/embedContext";
import { EmbedContextProvider } from "@/terminal/embedContext";
import { AutoAnonymousLogin } from "./accountMenu";
import { SidebarMdProvider } from "./sidebar";
import { RuntimeProvider } from "./terminal/runtime";
import { RuntimeProvider } from "@my-code/runtime/context";
import { getPagesList } from "@/lib/docs";

export const metadata: Metadata = {
Expand Down
4 changes: 2 additions & 2 deletions app/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
} from "react";
import { DynamicMarkdownSection } from "./[lang]/[pageId]/pageContent";
import clsx from "clsx";
import { LanguageIcon } from "./terminal/icons";
import { RuntimeLang } from "./terminal/runtime";
import { LanguageIcon } from "@/terminal/icons";
import { RuntimeLang } from "@my-code/runtime/languages";

export interface ISidebarMdContext {
loadedDocsId: { lang: string; pageId: string } | null;
Expand Down
Loading
Loading