Compare commits
38 Commits
Dockerfile
...
main
Author | SHA1 | Date | |
---|---|---|---|
ef3114e68b | |||
63fd9a4a45 | |||
1bdae1d458 | |||
2ac6457f01 | |||
0d29c56750 | |||
f4ce0e594d | |||
4e1a185036 | |||
3e94bf07a6 | |||
7ade2f8e90 | |||
bb06fafe78 | |||
c29e62b1e0 | |||
922872e3a9 | |||
3895a2e5b7 | |||
e903df067b | |||
5f79671aa5 | |||
9ef83d99b6 | |||
353aff9b4d | |||
3336a9f1d0 | |||
a33033b48d | |||
808dd96a50 | |||
d33f214450 | |||
2a47b469b0 | |||
470fa306c2 | |||
ca82b94b0f | |||
753422ebf4 | |||
527c52abbc | |||
0c94bb2fa3 | |||
a920cbc4b8 | |||
398928d933 | |||
d89a45daa8 | |||
0c1ecbcff2 | |||
598ca75829 | |||
af23dd3289 | |||
742a827072 | |||
1f417fb4e6 | |||
6d9c6701d4 | |||
ec9f3b2475 | |||
d8fa37dd8a |
@ -1,6 +1,11 @@
|
|||||||
.git
|
.git
|
||||||
|
.github
|
||||||
.next
|
.next
|
||||||
node_modules
|
node_modules
|
||||||
.dockerignore
|
.dockerignore
|
||||||
.gitignore
|
.gitignore
|
||||||
Dockerfile
|
**/*compose*
|
||||||
|
demo.png
|
||||||
|
**/*Dockerfile*
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
49
.github/workflows/docker-build-and-push.yml
vendored
Normal file
49
.github/workflows/docker-build-and-push.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
name: Docker Build and Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker-build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- image: monaco-editor-lsp-next
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
- image: lsp-c
|
||||||
|
context: ./docker/lsp/clangd
|
||||||
|
file: ./docker/lsp/clangd/Dockerfile
|
||||||
|
- image: lsp-cpp
|
||||||
|
context: ./docker/lsp/clangd
|
||||||
|
file: ./docker/lsp/clangd/Dockerfile
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
# - name: Set up QEMU
|
||||||
|
# uses: docker/setup-qemu-action@v3
|
||||||
|
# with:
|
||||||
|
# platforms: amd64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push ${{ matrix.image }}
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ${{ matrix.context }}
|
||||||
|
file: ${{ matrix.file }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ vars.DOCKERHUB_USERNAME }}/${{ matrix.image }}:latest
|
||||||
|
# platforms: linux/amd64
|
10
Dockerfile
10
Dockerfile
@ -16,7 +16,7 @@ RUN \
|
|||||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
elif [ -f package-lock.json ]; then npm ci; \
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||||
elif [ -f bun.lock ]; then \
|
elif [ -f bun.lock ] || [ -f bun.lockb ]; then \
|
||||||
apk add --no-cache curl bash && \
|
apk add --no-cache curl bash && \
|
||||||
curl -fsSL https://bun.sh/install | bash && \
|
curl -fsSL https://bun.sh/install | bash && \
|
||||||
export BUN_INSTALL="$HOME/.bun" && \
|
export BUN_INSTALL="$HOME/.bun" && \
|
||||||
@ -25,7 +25,6 @@ RUN \
|
|||||||
else echo "Lockfile not found." && exit 1; \
|
else echo "Lockfile not found." && exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
# Rebuild the source code only when needed
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -41,7 +40,7 @@ RUN \
|
|||||||
if [ -f yarn.lock ]; then yarn run build; \
|
if [ -f yarn.lock ]; then yarn run build; \
|
||||||
elif [ -f package-lock.json ]; then npm run build; \
|
elif [ -f package-lock.json ]; then npm run build; \
|
||||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||||
elif [ -f bun.lock ]; then \
|
elif [ -f bun.lock ] || [ -f bun.lockb ]; then \
|
||||||
apk add --no-cache curl bash && \
|
apk add --no-cache curl bash && \
|
||||||
curl -fsSL https://bun.sh/install | bash && \
|
curl -fsSL https://bun.sh/install | bash && \
|
||||||
export BUN_INSTALL="$HOME/.bun" && \
|
export BUN_INSTALL="$HOME/.bun" && \
|
||||||
@ -61,9 +60,6 @@ ENV NODE_ENV=production
|
|||||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
# Automatically leverage output traces to reduce image size
|
# Automatically leverage output traces to reduce image size
|
||||||
@ -71,7 +67,7 @@ COPY --from=builder /app/public ./public
|
|||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
USER nextjs
|
USER root
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
41
README.md
41
README.md
@ -26,29 +26,46 @@ Complete these steps before launching the editor for seamless LSP integration!
|
|||||||
|
|
||||||
### 🐳 Docker Deployment (Recommended)
|
### 🐳 Docker Deployment (Recommended)
|
||||||
|
|
||||||
|
Deploy the project quickly using pre-built Docker images:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Clone repository
|
# Clone the repository
|
||||||
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
||||||
cd monaco-editor-lsp-next
|
cd monaco-editor-lsp-next
|
||||||
|
|
||||||
# Build and launch containers
|
# Start the application with pre-built images
|
||||||
docker compose up -d --build
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🔨 Manual Installation
|
### 🔨 Local Manual Build
|
||||||
|
|
||||||
|
Build the images locally and start the containers:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Clone repository
|
# Clone the repository
|
||||||
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
||||||
cd monaco-editor-lsp-next
|
cd monaco-editor-lsp-next
|
||||||
|
|
||||||
# Start core LSP containers
|
# Build and start containers using the local configuration
|
||||||
docker compose up -d --build lsp-c lsp-cpp
|
docker compose -f compose.local.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
# Install dependencies (using Bun)
|
### 🛠 Development Mode
|
||||||
|
|
||||||
|
Set up a development environment with hot-reloading:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
||||||
|
cd monaco-editor-lsp-next
|
||||||
|
|
||||||
|
# Start only the LSP containers
|
||||||
|
docker compose up -d lsp-c lsp-cpp
|
||||||
|
# Or use the local configuration
|
||||||
|
# docker compose -f compose.local.yml up -d --build lsp-c lsp-cpp
|
||||||
|
|
||||||
|
# Install dependencies and start the development server
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Launch development server
|
|
||||||
bun run dev
|
bun run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -97,10 +114,12 @@ bun run dev
|
|||||||
Syntax highlighting depends on `rehype-pretty-code`'s deprecated `getHighlighter` API from `shiki@legacy`.
|
Syntax highlighting depends on `rehype-pretty-code`'s deprecated `getHighlighter` API from `shiki@legacy`.
|
||||||
|
|
||||||
**Key Points**:
|
**Key Points**:
|
||||||
|
|
||||||
- **Affected File:** `src/components/mdx-preview.tsx`
|
- **Affected File:** `src/components/mdx-preview.tsx`
|
||||||
- **Dependency Chain:** `rehype-pretty-code` → `shiki@legacy`
|
- **Dependency Chain:** `rehype-pretty-code` → `shiki@legacy`
|
||||||
- **Version Constraints**:
|
- **Version Constraints**:
|
||||||
```bash
|
|
||||||
|
```sh
|
||||||
"shiki": "<=2.5.0"
|
"shiki": "<=2.5.0"
|
||||||
"@shikijs/monaco": "<=2.5.0"
|
"@shikijs/monaco": "<=2.5.0"
|
||||||
```
|
```
|
||||||
|
62
compose.local.yml
Normal file
62
compose.local.yml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
services:
|
||||||
|
monaco-editor-lsp-next:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: monaco-editor-lsp-next:latest
|
||||||
|
container_name: monaco-editor-lsp-next
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
networks:
|
||||||
|
- monaco-editor-lsp-next
|
||||||
|
depends_on:
|
||||||
|
- lsp-c
|
||||||
|
- lsp-cpp
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "curl --fail http://localhost:3000 || exit 1" ]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
lsp-c:
|
||||||
|
build:
|
||||||
|
context: ./docker/lsp/clangd
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: lsp-c:latest
|
||||||
|
container_name: lsp-c
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "4594:3000"
|
||||||
|
networks:
|
||||||
|
- monaco-editor-lsp-next
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "nc -zv localhost 3000" ]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
lsp-cpp:
|
||||||
|
build:
|
||||||
|
context: ./docker/lsp/clangd
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: lsp-cpp:latest
|
||||||
|
container_name: lsp-cpp
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "4595:3000"
|
||||||
|
networks:
|
||||||
|
- monaco-editor-lsp-next
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "nc -zv localhost 3000" ]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
networks:
|
||||||
|
monaco-editor-lsp-next:
|
||||||
|
name: monaco-editor-lsp-next
|
||||||
|
driver: bridge
|
16
compose.yml
16
compose.yml
@ -1,9 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
monaco-editor-lsp-next:
|
monaco-editor-lsp-next:
|
||||||
build:
|
image: cfngc4594/monaco-editor-lsp-next:latest
|
||||||
context: ./
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
image: monaco-editor-lsp-next:latest
|
|
||||||
container_name: monaco-editor-lsp-next
|
container_name: monaco-editor-lsp-next
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
@ -19,15 +16,11 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
user: root
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
lsp-c:
|
lsp-c:
|
||||||
build:
|
image: cfngc4594/lsp-c:latest
|
||||||
context: ./docker/lsp/clangd
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
image: lsp-c:latest
|
|
||||||
container_name: lsp-c
|
container_name: lsp-c
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
@ -41,10 +34,7 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
lsp-cpp:
|
lsp-cpp:
|
||||||
build:
|
image: cfngc4594/lsp-cpp:latest
|
||||||
context: ./docker/lsp/clangd
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
image: lsp-cpp:latest
|
|
||||||
container_name: lsp-cpp
|
container_name: lsp-cpp
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
BIN
demo.png
BIN
demo.png
Binary file not shown.
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 243 KiB |
@ -1,10 +1,10 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import LanguageSelector from "./language-selector";
|
|
||||||
import FormatButton from "./format-button";
|
|
||||||
import CopyButton from "./copy-button";
|
import CopyButton from "./copy-button";
|
||||||
import RedoButton from "./redo-button";
|
import RedoButton from "./redo-button";
|
||||||
import UndoButton from "./undo-button";
|
import UndoButton from "./undo-button";
|
||||||
import ResetButton from "./reset-button";
|
import ResetButton from "./reset-button";
|
||||||
|
import FormatButton from "./format-button";
|
||||||
|
import LanguageSelector from "./language-selector";
|
||||||
|
|
||||||
interface WorkspaceEditorHeaderProps {
|
interface WorkspaceEditorHeaderProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -7,28 +7,37 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { getPath } from "@/lib/utils";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
import LanguageServerConfig from "@/config/language-server";
|
||||||
|
import { EditorLanguageConfig } from "@/config/editor-language";
|
||||||
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
||||||
import { SUPPORTED_LANGUAGES } from "@/constants/language";
|
|
||||||
|
|
||||||
export default function LanguageSelector() {
|
export default function LanguageSelector() {
|
||||||
const { hydrated, language, setLanguage } = useCodeEditorStore();
|
const { hydrated, language, setLanguage, setPath, setLspConfig } = useCodeEditorStore();
|
||||||
|
|
||||||
if (!hydrated) {
|
if (!hydrated) {
|
||||||
return <Skeleton className="h-6 w-16 rounded-2xl" />;
|
return <Skeleton className="h-6 w-16 rounded-2xl" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleValueChange = (lang: EditorLanguage) => {
|
||||||
|
setLanguage(lang);
|
||||||
|
setPath(getPath(lang));
|
||||||
|
setLspConfig(LanguageServerConfig[lang]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select value={language} onValueChange={setLanguage}>
|
<Select value={language} onValueChange={handleValueChange}>
|
||||||
<SelectTrigger className="h-6 px-1.5 py-0.5 border-none focus:ring-0 hover:bg-muted [&>span]:flex [&>span]:items-center [&>span]:gap-2 [&>span_svg]:shrink-0 [&>span_svg]:text-muted-foreground/80">
|
<SelectTrigger className="h-6 px-1.5 py-0.5 border-none focus:ring-0 hover:bg-muted [&>span]:flex [&>span]:items-center [&>span]:gap-2 [&>span_svg]:shrink-0 [&>span_svg]:text-muted-foreground/80">
|
||||||
<SelectValue placeholder="Select language" />
|
<SelectValue placeholder="Select language" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="[&_*[role=option]>span>svg]:shrink-0 [&_*[role=option]>span>svg]:text-muted-foreground/80 [&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:flex [&_*[role=option]>span]:items-center [&_*[role=option]>span]:gap-2 [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2">
|
<SelectContent className="[&_*[role=option]>span>svg]:shrink-0 [&_*[role=option]>span>svg]:text-muted-foreground/80 [&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:flex [&_*[role=option]>span]:items-center [&_*[role=option]>span]:gap-2 [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2">
|
||||||
{SUPPORTED_LANGUAGES.map((lang) => (
|
{Object.values(EditorLanguageConfig).map((langConfig) => (
|
||||||
<SelectItem key={lang.id} value={lang.id}>
|
<SelectItem key={langConfig.id} value={langConfig.id}>
|
||||||
{lang.icon}
|
<langConfig.icon size={16} aria-hidden="true" />
|
||||||
<span className="truncate text-sm font-semibold mr-2">
|
<span className="truncate text-sm font-semibold mr-2">
|
||||||
{lang.label}
|
{langConfig.label}
|
||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RotateCcw } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { DEFAULT_EDITOR_VALUE } from "@/config/editor/value";
|
import { RotateCcw } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
||||||
|
import { TEMP_DEFAULT_EDITOR_VALUE } from "@/config/problem/value";
|
||||||
|
|
||||||
export default function ResetButton() {
|
export default function ResetButton() {
|
||||||
const { editor, language } = useCodeEditorStore();
|
const { editor, language } = useCodeEditorStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@ -24,7 +23,7 @@ export default function ResetButton() {
|
|||||||
aria-label="Reset Code"
|
aria-label="Reset Code"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
const value = DEFAULT_EDITOR_VALUE[language];
|
const value = TEMP_DEFAULT_EDITOR_VALUE[language];
|
||||||
const model = editor.getModel();
|
const model = editor.getModel();
|
||||||
if (model) {
|
if (model) {
|
||||||
const fullRange = model.getFullModelRange();
|
const fullRange = model.getFullModelRange();
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
import tar from "tar-stream";
|
import tar from "tar-stream";
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import { Readable, Writable } from "stream";
|
import { Readable, Writable } from "stream";
|
||||||
import { ExitCode, JudgeResult, LanguageConfigs } from "@/config/judge";
|
import { JudgeConfig } from "@/config/judge";
|
||||||
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
import { ExitCode, JudgeResultMetadata } from "@/types/judge";
|
||||||
|
|
||||||
// Docker client initialization
|
// Docker client initialization
|
||||||
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||||
@ -41,10 +43,11 @@ function createTarStream(file: string, value: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function judge(
|
export async function judge(
|
||||||
language: string,
|
language: EditorLanguage,
|
||||||
value: string
|
value: string
|
||||||
): Promise<JudgeResult> {
|
): Promise<JudgeResultMetadata> {
|
||||||
const { fileName, fileExtension, image, tag, workingDir, memoryLimit, timeLimit, compileOutputLimit, runOutputLimit } = LanguageConfigs[language];
|
const { fileName, fileExtension } = JudgeConfig[language].editorLanguageMetadata;
|
||||||
|
const { image, tag, workingDir, memoryLimit, timeLimit, compileOutputLimit, runOutputLimit } = JudgeConfig[language].dockerMetadata;
|
||||||
const file = `${fileName}.${fileExtension}`;
|
const file = `${fileName}.${fileExtension}`;
|
||||||
let container: Docker.Container | undefined;
|
let container: Docker.Container | undefined;
|
||||||
|
|
||||||
@ -70,14 +73,14 @@ export async function judge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compile(container: Docker.Container, file: string, fileName: string, maxOutput: number = 1 * 1024 * 1024): Promise<JudgeResult> {
|
async function compile(container: Docker.Container, file: string, fileName: string, maxOutput: number = 1 * 1024 * 1024): Promise<JudgeResultMetadata> {
|
||||||
const compileExec = await container.exec({
|
const compileExec = await container.exec({
|
||||||
Cmd: ["gcc", "-O2", file, "-o", fileName],
|
Cmd: ["gcc", "-O2", file, "-o", fileName],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<JudgeResult>((resolve, reject) => {
|
return new Promise<JudgeResultMetadata>((resolve, reject) => {
|
||||||
compileExec.start({}, (error, stream) => {
|
compileExec.start({}, (error, stream) => {
|
||||||
if (error || !stream) {
|
if (error || !stream) {
|
||||||
return reject({ output: "System Error", exitCode: ExitCode.SE });
|
return reject({ output: "System Error", exitCode: ExitCode.SE });
|
||||||
@ -126,7 +129,7 @@ async function compile(container: Docker.Container, file: string, fileName: stri
|
|||||||
const stderr = stderrChunks.join("");
|
const stderr = stderrChunks.join("");
|
||||||
const exitCode = (await compileExec.inspect()).ExitCode;
|
const exitCode = (await compileExec.inspect()).ExitCode;
|
||||||
|
|
||||||
let result: JudgeResult;
|
let result: JudgeResultMetadata;
|
||||||
|
|
||||||
if (exitCode !== 0 || stderr) {
|
if (exitCode !== 0 || stderr) {
|
||||||
result = { output: stderr || "Compilation Error", exitCode: ExitCode.CE };
|
result = { output: stderr || "Compilation Error", exitCode: ExitCode.CE };
|
||||||
@ -145,14 +148,14 @@ async function compile(container: Docker.Container, file: string, fileName: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run code and implement timeout
|
// Run code and implement timeout
|
||||||
async function run(container: Docker.Container, fileName: string, timeLimit?: number, maxOutput: number = 1 * 1024 * 1024): Promise<JudgeResult> {
|
async function run(container: Docker.Container, fileName: string, timeLimit?: number, maxOutput: number = 1 * 1024 * 1024): Promise<JudgeResultMetadata> {
|
||||||
const runExec = await container.exec({
|
const runExec = await container.exec({
|
||||||
Cmd: [`./${fileName}`],
|
Cmd: [`./${fileName}`],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<JudgeResult>((resolve, reject) => {
|
return new Promise<JudgeResultMetadata>((resolve, reject) => {
|
||||||
const stdoutChunks: string[] = [];
|
const stdoutChunks: string[] = [];
|
||||||
let stdoutLength = 0;
|
let stdoutLength = 0;
|
||||||
const stdoutStream = new Writable({
|
const stdoutStream = new Writable({
|
||||||
@ -211,7 +214,7 @@ async function run(container: Docker.Container, fileName: string, timeLimit?: nu
|
|||||||
const stderr = stderrChunks.join("");
|
const stderr = stderrChunks.join("");
|
||||||
const exitCode = (await runExec.inspect()).ExitCode;
|
const exitCode = (await runExec.inspect()).ExitCode;
|
||||||
|
|
||||||
let result: JudgeResult;
|
let result: JudgeResultMetadata;
|
||||||
|
|
||||||
// Exit code 0 means successful execution
|
// Exit code 0 means successful execution
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
|
@ -1,240 +1,127 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
|
||||||
toSocket,
|
|
||||||
WebSocketMessageReader,
|
|
||||||
WebSocketMessageWriter,
|
|
||||||
} from "vscode-ws-jsonrpc";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useTheme } from "next-themes";
|
import { Skeleton } from "./ui/skeleton";
|
||||||
import normalizeUrl from "normalize-url";
|
|
||||||
import { highlighter } from "@/lib/shiki";
|
import { highlighter } from "@/lib/shiki";
|
||||||
import { useEffect, useRef } from "react";
|
import type { editor } from "monaco-editor";
|
||||||
import { shikiToMonaco } from "@shikijs/monaco";
|
import { shikiToMonaco } from "@shikijs/monaco";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import type { Monaco } from "@monaco-editor/react";
|
||||||
import { CODE_EDITOR_OPTIONS } from "@/constants/option";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import { DEFAULT_EDITOR_PATH } from "@/config/editor/path";
|
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
||||||
import { DEFAULT_EDITOR_VALUE } from "@/config/editor/value";
|
import LanguageServerConfig from "@/config/language-server";
|
||||||
|
import { connectToLanguageServer } from "@/lib/language-server";
|
||||||
|
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
||||||
import type { MonacoLanguageClient } from "monaco-languageclient";
|
import type { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
import { SUPPORTED_LANGUAGE_SERVERS } from "@/config/lsp/language-server";
|
|
||||||
import { useCodeEditorOptionStore, useCodeEditorStore } from "@/store/useCodeEditorStore";
|
|
||||||
|
|
||||||
|
// Skeleton component for loading state
|
||||||
|
const CodeEditorLoadingSkeleton = () => (
|
||||||
|
<div className="h-full w-full p-2">
|
||||||
|
<Skeleton className="h-full w-full rounded-3xl" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dynamically import Monaco Editor with SSR disabled
|
||||||
const Editor = dynamic(
|
const Editor = dynamic(
|
||||||
async () => {
|
async () => {
|
||||||
await import("vscode");
|
await import("vscode");
|
||||||
|
|
||||||
const monaco = await import("monaco-editor");
|
const monaco = await import("monaco-editor");
|
||||||
const { loader } = await import("@monaco-editor/react");
|
const { loader } = await import("@monaco-editor/react");
|
||||||
loader.config({ monaco });
|
loader.config({ monaco });
|
||||||
|
|
||||||
return (await import("@monaco-editor/react")).Editor;
|
return (await import("@monaco-editor/react")).Editor;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ssr: false,
|
ssr: false,
|
||||||
loading: () => (
|
loading: () => <CodeEditorLoadingSkeleton />,
|
||||||
<div className="h-full w-full p-4">
|
|
||||||
<Skeleton className="h-full w-full rounded-3xl" />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
type ConnectionHandle = {
|
|
||||||
client: MonacoLanguageClient | null;
|
|
||||||
socket: WebSocket | null;
|
|
||||||
controller: AbortController;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function CodeEditor() {
|
export default function CodeEditor() {
|
||||||
const { resolvedTheme } = useTheme();
|
const {
|
||||||
const connectionRef = useRef<ConnectionHandle>({
|
hydrated,
|
||||||
client: null,
|
language,
|
||||||
socket: null,
|
path,
|
||||||
controller: new AbortController(),
|
value,
|
||||||
});
|
editorConfig,
|
||||||
const { fontSize, lineHeight } = useCodeEditorOptionStore();
|
isLspEnabled,
|
||||||
const { language, setEditor } = useCodeEditorStore();
|
setEditor,
|
||||||
|
} = useCodeEditorStore();
|
||||||
|
const { monacoTheme } = useMonacoTheme();
|
||||||
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
|
const monacoLanguageClientRef = useRef<MonacoLanguageClient | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
// Connect to LSP only if enabled
|
||||||
const currentHandle: ConnectionHandle = {
|
const connectLSP = useCallback(async () => {
|
||||||
client: null,
|
if (!(isLspEnabled && language && editorRef.current)) return;
|
||||||
socket: null,
|
|
||||||
controller: new AbortController(),
|
|
||||||
};
|
|
||||||
const signal = currentHandle.controller.signal;
|
|
||||||
connectionRef.current = currentHandle;
|
|
||||||
|
|
||||||
const cleanupConnection = async (handle: ConnectionHandle) => {
|
const lspConfig = LanguageServerConfig[language];
|
||||||
try {
|
|
||||||
// Cleanup Language Client
|
|
||||||
if (handle.client) {
|
|
||||||
console.log("Stopping language client...");
|
|
||||||
await handle.client.stop(250).catch(() => { });
|
|
||||||
handle.client.dispose();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Client cleanup error:", e);
|
|
||||||
} finally {
|
|
||||||
handle.client = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup WebSocket
|
if (!lspConfig) return;
|
||||||
if (handle.socket) {
|
|
||||||
console.log("Closing WebSocket...");
|
|
||||||
const socket = handle.socket;
|
|
||||||
socket.onopen = null;
|
|
||||||
socket.onerror = null;
|
|
||||||
socket.onclose = null;
|
|
||||||
socket.onmessage = null;
|
|
||||||
|
|
||||||
try {
|
// If there's an existing language client, stop it first
|
||||||
if (
|
if (monacoLanguageClientRef.current) {
|
||||||
[WebSocket.OPEN, WebSocket.CONNECTING].includes(
|
monacoLanguageClientRef.current.stop();
|
||||||
socket.readyState as WebSocket["OPEN"] | WebSocket["CONNECTING"]
|
monacoLanguageClientRef.current = null;
|
||||||
)
|
|
||||||
) {
|
|
||||||
socket.close(1000, "Connection replaced");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Socket close error:", e);
|
|
||||||
} finally {
|
|
||||||
handle.socket = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialize = async () => {
|
|
||||||
try {
|
|
||||||
// Cleanup old connection
|
|
||||||
await cleanupConnection(connectionRef.current);
|
|
||||||
|
|
||||||
const serverConfig = SUPPORTED_LANGUAGE_SERVERS.find(
|
|
||||||
(s) => s.id === language
|
|
||||||
);
|
|
||||||
if (!serverConfig || signal.aborted) return;
|
|
||||||
|
|
||||||
// Create WebSocket connection
|
|
||||||
const lspUrl = `${serverConfig.protocol}://${serverConfig.hostname}${serverConfig.port ? `:${serverConfig.port}` : ""}${serverConfig.path || ""}`;
|
|
||||||
const webSocket = new WebSocket(normalizeUrl(lspUrl));
|
|
||||||
currentHandle.socket = webSocket;
|
|
||||||
|
|
||||||
// Wait for connection to establish or timeout
|
|
||||||
await Promise.race([
|
|
||||||
new Promise<void>((resolve, reject) => {
|
|
||||||
webSocket.onopen = () => {
|
|
||||||
if (signal.aborted) reject(new Error("Connection aborted"));
|
|
||||||
else resolve();
|
|
||||||
};
|
|
||||||
webSocket.onerror = () => reject(new Error("WebSocket error"));
|
|
||||||
}),
|
|
||||||
new Promise((_, reject) =>
|
|
||||||
setTimeout(() => reject(new Error("Connection timeout")), 5000)
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (signal.aborted) {
|
|
||||||
webSocket.close(1001, "Connection aborted");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Language Client
|
|
||||||
const { MonacoLanguageClient } = await import("monaco-languageclient");
|
|
||||||
const { ErrorAction, CloseAction } = await import("vscode-languageclient");
|
|
||||||
|
|
||||||
const socket = toSocket(webSocket);
|
|
||||||
const client = new MonacoLanguageClient({
|
|
||||||
name: `${serverConfig.label} Client`,
|
|
||||||
clientOptions: {
|
|
||||||
documentSelector: [serverConfig.id],
|
|
||||||
errorHandler: {
|
|
||||||
error: () => ({ action: ErrorAction.Continue }),
|
|
||||||
closed: () => ({ action: CloseAction.DoNotRestart }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
connectionProvider: {
|
|
||||||
get: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
reader: new WebSocketMessageReader(socket),
|
|
||||||
writer: new WebSocketMessageWriter(socket),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
client.start();
|
|
||||||
currentHandle.client = client;
|
|
||||||
|
|
||||||
// Bind WebSocket close event
|
|
||||||
webSocket.onclose = (event) => {
|
|
||||||
if (!signal.aborted) {
|
|
||||||
console.log("WebSocket closed:", event);
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (!signal.aborted) {
|
|
||||||
console.error("Connection failed:", error);
|
|
||||||
}
|
|
||||||
cleanupConnection(currentHandle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log("Cleanup triggered");
|
|
||||||
currentHandle.controller.abort();
|
|
||||||
cleanupConnection(currentHandle);
|
|
||||||
};
|
|
||||||
}, [language]);
|
|
||||||
|
|
||||||
const mergeOptions = {
|
|
||||||
...CODE_EDITOR_OPTIONS,
|
|
||||||
fontSize,
|
|
||||||
lineHeight,
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleEditorChange(value: string | undefined) {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
localStorage.setItem(`code-editor-value-${language}`, value ?? "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new language client
|
||||||
|
try {
|
||||||
|
const monacoLanguageClient = await connectToLanguageServer(
|
||||||
|
lspConfig.protocol,
|
||||||
|
lspConfig.hostname,
|
||||||
|
lspConfig.port,
|
||||||
|
lspConfig.path,
|
||||||
|
lspConfig.lang
|
||||||
|
);
|
||||||
|
monacoLanguageClientRef.current = monacoLanguageClient;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to connect to LSP:", error);
|
||||||
|
}
|
||||||
|
}, [isLspEnabled, language]);
|
||||||
|
|
||||||
|
// Connect to LSP once the editor has mounted
|
||||||
|
const handleEditorDidMount = useCallback(
|
||||||
|
async (editor: editor.IStandaloneCodeEditor) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
await connectLSP();
|
||||||
|
setEditor(editor);
|
||||||
|
},
|
||||||
|
[connectLSP, setEditor]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reconnect to the LSP whenever language or lspConfig changes
|
||||||
|
useEffect(() => {
|
||||||
|
connectLSP();
|
||||||
|
}, [connectLSP]);
|
||||||
|
|
||||||
|
// Cleanup the LSP connection when the component unmounts
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (monacoLanguageClientRef.current) {
|
||||||
|
monacoLanguageClientRef.current.stop();
|
||||||
|
monacoLanguageClientRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!hydrated) {
|
||||||
|
return <CodeEditorLoadingSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorValue =
|
function handleEditorWillMount(monaco: Monaco) {
|
||||||
typeof window !== "undefined"
|
shikiToMonaco(highlighter, monaco);
|
||||||
? localStorage.getItem(`code-editor-value-${language}`) ||
|
}
|
||||||
DEFAULT_EDITOR_VALUE[language]
|
|
||||||
: DEFAULT_EDITOR_VALUE[language];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
defaultLanguage={language}
|
language={language}
|
||||||
value={editorValue}
|
theme={monacoTheme.id}
|
||||||
path={DEFAULT_EDITOR_PATH[language]}
|
path={path}
|
||||||
theme={resolvedTheme === "light" ? "github-light-default" : "github-dark-default"}
|
value={value}
|
||||||
className="h-full"
|
beforeMount={handleEditorWillMount}
|
||||||
options={mergeOptions}
|
onMount={handleEditorDidMount}
|
||||||
beforeMount={(monaco) => {
|
options={editorConfig}
|
||||||
shikiToMonaco(highlighter, monaco);
|
loading={<CodeEditorLoadingSkeleton />}
|
||||||
}}
|
className="h-full w-full py-2"
|
||||||
onMount={(editor) => {
|
|
||||||
setEditor(editor);
|
|
||||||
}}
|
|
||||||
onChange={handleEditorChange}
|
|
||||||
// onValidate={(markers) => {
|
|
||||||
// markers.forEach((marker) => {
|
|
||||||
// console.log(marker.severity);
|
|
||||||
// console.log(marker.startLineNumber);
|
|
||||||
// console.log(marker.startColumn);
|
|
||||||
// console.log(marker.endLineNumber);
|
|
||||||
// console.log(marker.endColumn);
|
|
||||||
// console.log(marker.message);
|
|
||||||
// });
|
|
||||||
// }}
|
|
||||||
loading={
|
|
||||||
<div className="h-full w-full p-4">
|
|
||||||
<Skeleton className="h-full w-full rounded-3xl" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
interface CodeBlockWithCopyProps {
|
interface CodeBlockWithCopyProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
25
src/config/editor-language.ts
Normal file
25
src/config/editor-language.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { COriginal, CplusplusOriginal } from "devicons-react";
|
||||||
|
import { EditorLanguage, EditorLanguageMetadata } from "@/types/editor-language";
|
||||||
|
|
||||||
|
// Define language configurations
|
||||||
|
const EditorLanguageConfig: Record<EditorLanguage, EditorLanguageMetadata> = {
|
||||||
|
[EditorLanguage.C]: {
|
||||||
|
id: EditorLanguage.C,
|
||||||
|
label: "C",
|
||||||
|
fileName: "main",
|
||||||
|
fileExtension: ".c",
|
||||||
|
icon: COriginal,
|
||||||
|
},
|
||||||
|
[EditorLanguage.CPP]: {
|
||||||
|
id: EditorLanguage.CPP,
|
||||||
|
label: "C++",
|
||||||
|
fileName: "main",
|
||||||
|
fileExtension: ".cpp",
|
||||||
|
icon: CplusplusOriginal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default language configuration
|
||||||
|
const DefaultEditorLanguageConfig = EditorLanguageConfig[EditorLanguage.C]; // Default to C language
|
||||||
|
|
||||||
|
export { EditorLanguageConfig, DefaultEditorLanguageConfig };
|
@ -1,6 +1,6 @@
|
|||||||
import { type editor } from "monaco-editor";
|
import { type editor } from "monaco-editor";
|
||||||
|
|
||||||
export const CODE_EDITOR_OPTIONS: editor.IEditorConstructionOptions = {
|
export const DefaultEditorOptionConfig: editor.IEditorConstructionOptions = {
|
||||||
autoIndent: "full",
|
autoIndent: "full",
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
contextmenu: true,
|
contextmenu: true,
|
||||||
@ -15,16 +15,13 @@ export const CODE_EDITOR_OPTIONS: editor.IEditorConstructionOptions = {
|
|||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
matchBrackets: "always",
|
matchBrackets: "always",
|
||||||
minimap: {
|
minimap: {
|
||||||
enabled: false
|
enabled: false,
|
||||||
},
|
|
||||||
padding: {
|
|
||||||
top: 8
|
|
||||||
},
|
},
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
horizontalSliderSize: 10,
|
horizontalSliderSize: 10,
|
||||||
verticalSliderSize: 10
|
verticalSliderSize: 10,
|
||||||
},
|
},
|
||||||
showFoldingControls: "always",
|
showFoldingControls: "always",
|
||||||
wordWrap: "on",
|
wordWrap: "on",
|
||||||
}
|
};
|
@ -1,3 +0,0 @@
|
|||||||
import { SUPPORTED_LANGUAGES, SupportedLanguage } from "@/constants/language";
|
|
||||||
|
|
||||||
export const DEFAULT_EDITOR_LANGUAGE: SupportedLanguage = SUPPORTED_LANGUAGES[0].id;
|
|
@ -1,6 +0,0 @@
|
|||||||
import { SupportedLanguage } from "@/constants/language";
|
|
||||||
|
|
||||||
export const DEFAULT_EDITOR_PATH: Record<SupportedLanguage, string> = {
|
|
||||||
c: "file:///main.c",
|
|
||||||
cpp: "file:///main.cpp",
|
|
||||||
};
|
|
@ -1,42 +1,9 @@
|
|||||||
// Result type definitions
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
export enum ExitCode {
|
import { EditorLanguageConfig } from "./editor-language";
|
||||||
SE = 0, // System Error
|
import { DockerMetadata, JudgeMetadata } from "@/types/judge";
|
||||||
CS = 1, // Compilation Success
|
|
||||||
CE = 2, // Compilation Error
|
|
||||||
TLE = 3, // Time Limit Exceeded
|
|
||||||
MLE = 4, // Memory Limit Exceeded
|
|
||||||
RE = 5, // Runtime Error
|
|
||||||
AC = 6, // Accepted
|
|
||||||
WA = 7, // Wrong Answer
|
|
||||||
}
|
|
||||||
|
|
||||||
export type JudgeResult = {
|
export const DockerConfig: Record<EditorLanguage, DockerMetadata> = {
|
||||||
output: string;
|
[EditorLanguage.C]: {
|
||||||
exitCode: ExitCode;
|
|
||||||
executionTime?: number;
|
|
||||||
memoryUsage?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface LanguageConfig {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
fileName: string;
|
|
||||||
fileExtension: string;
|
|
||||||
image: string;
|
|
||||||
tag: string;
|
|
||||||
workingDir: string;
|
|
||||||
timeLimit: number;
|
|
||||||
memoryLimit: number;
|
|
||||||
compileOutputLimit: number;
|
|
||||||
runOutputLimit: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LanguageConfigs: Record<string, LanguageConfig> = {
|
|
||||||
c: {
|
|
||||||
id: "c",
|
|
||||||
label: "C",
|
|
||||||
fileName: "main",
|
|
||||||
fileExtension: "c",
|
|
||||||
image: "gcc",
|
image: "gcc",
|
||||||
tag: "latest",
|
tag: "latest",
|
||||||
workingDir: "/src",
|
workingDir: "/src",
|
||||||
@ -45,11 +12,7 @@ export const LanguageConfigs: Record<string, LanguageConfig> = {
|
|||||||
compileOutputLimit: 1 * 1024 * 1024,
|
compileOutputLimit: 1 * 1024 * 1024,
|
||||||
runOutputLimit: 1 * 1024 * 1024,
|
runOutputLimit: 1 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
cpp: {
|
[EditorLanguage.CPP]: {
|
||||||
id: "cpp",
|
|
||||||
label: "C++",
|
|
||||||
fileName: "main",
|
|
||||||
fileExtension: "cpp",
|
|
||||||
image: "gcc",
|
image: "gcc",
|
||||||
tag: "latest",
|
tag: "latest",
|
||||||
workingDir: "/src",
|
workingDir: "/src",
|
||||||
@ -57,5 +20,16 @@ export const LanguageConfigs: Record<string, LanguageConfig> = {
|
|||||||
memoryLimit: 128,
|
memoryLimit: 128,
|
||||||
compileOutputLimit: 1 * 1024 * 1024,
|
compileOutputLimit: 1 * 1024 * 1024,
|
||||||
runOutputLimit: 1 * 1024 * 1024,
|
runOutputLimit: 1 * 1024 * 1024,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JudgeConfig: Record<EditorLanguage, JudgeMetadata> = {
|
||||||
|
[EditorLanguage.C]: {
|
||||||
|
editorLanguageMetadata: EditorLanguageConfig[EditorLanguage.C],
|
||||||
|
dockerMetadata: DockerConfig[EditorLanguage.C],
|
||||||
|
},
|
||||||
|
[EditorLanguage.CPP]: {
|
||||||
|
editorLanguageMetadata: EditorLanguageConfig[EditorLanguage.CPP],
|
||||||
|
dockerMetadata: DockerConfig[EditorLanguage.CPP],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
import { SupportedLanguage } from '@/constants/language'
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
import { EditorLanguageConfig } from "./editor-language";
|
||||||
|
import { LanguageServerMetadata } from "@/types/language-server";
|
||||||
|
|
||||||
export interface LanguageServerConfig {
|
const LanguageServerConfig: Record<EditorLanguage, LanguageServerMetadata> = {
|
||||||
id: SupportedLanguage
|
[EditorLanguage.C]: {
|
||||||
label: string
|
|
||||||
hostname: string
|
|
||||||
protocol: string
|
|
||||||
port: number | null
|
|
||||||
path: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SUPPORTED_LANGUAGE_SERVERS: LanguageServerConfig[] = [
|
|
||||||
{
|
|
||||||
id: "c",
|
|
||||||
label: "C",
|
|
||||||
protocol: process.env.NEXT_PUBLIC_LSP_C_PROTOCOL || "ws",
|
protocol: process.env.NEXT_PUBLIC_LSP_C_PROTOCOL || "ws",
|
||||||
hostname: process.env.NEXT_PUBLIC_LSP_C_HOSTNAME || "localhost",
|
hostname: process.env.NEXT_PUBLIC_LSP_C_HOSTNAME || "localhost",
|
||||||
port: process.env.NEXT_PUBLIC_LSP_C_PORT ? parseInt(process.env.NEXT_PUBLIC_LSP_C_PORT, 10) : 4594,
|
port: process.env.NEXT_PUBLIC_LSP_C_PORT ? parseInt(process.env.NEXT_PUBLIC_LSP_C_PORT, 10) : 4594,
|
||||||
path: process.env.NEXT_PUBLIC_LSP_C_PATH || "/clangd",
|
path: process.env.NEXT_PUBLIC_LSP_C_PATH || "/clangd",
|
||||||
|
lang: EditorLanguageConfig[EditorLanguage.C],
|
||||||
},
|
},
|
||||||
{
|
[EditorLanguage.CPP]: {
|
||||||
id: "cpp",
|
|
||||||
label: "C++",
|
|
||||||
protocol: process.env.NEXT_PUBLIC_LSP_CPP_PROTOCOL || "ws",
|
protocol: process.env.NEXT_PUBLIC_LSP_CPP_PROTOCOL || "ws",
|
||||||
hostname: process.env.NEXT_PUBLIC_LSP_CPP_HOSTNAME || "localhost",
|
hostname: process.env.NEXT_PUBLIC_LSP_CPP_HOSTNAME || "localhost",
|
||||||
port: process.env.NEXT_PUBLIC_LSP_CPP_PORT ? parseInt(process.env.NEXT_PUBLIC_LSP_CPP_PORT, 10) : 4595,
|
port: process.env.NEXT_PUBLIC_LSP_CPP_PORT ? parseInt(process.env.NEXT_PUBLIC_LSP_CPP_PORT, 10) : 4595,
|
||||||
path: process.env.NEXT_PUBLIC_LSP_CPP_PATH || "/clangd",
|
path: process.env.NEXT_PUBLIC_LSP_CPP_PATH || "/clangd",
|
||||||
|
lang: EditorLanguageConfig[EditorLanguage.CPP],
|
||||||
},
|
},
|
||||||
];
|
};
|
||||||
|
|
||||||
|
export default LanguageServerConfig;
|
15
src/config/monaco-theme.ts
Normal file
15
src/config/monaco-theme.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { MonacoTheme } from "@/types/monaco-theme";
|
||||||
|
|
||||||
|
// Define theme configurations
|
||||||
|
const MonacoThemeConfig = {
|
||||||
|
[MonacoTheme.GitHubLightDefault]: {
|
||||||
|
id: MonacoTheme.GitHubLightDefault,
|
||||||
|
label: "Github Light Default",
|
||||||
|
},
|
||||||
|
[MonacoTheme.GitHubDarkDefault]: {
|
||||||
|
id: MonacoTheme.GitHubDarkDefault,
|
||||||
|
label: "Github Dark Default",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { MonacoThemeConfig };
|
@ -1,13 +1,13 @@
|
|||||||
import { SupportedLanguage } from "@/constants/language";
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
|
||||||
export const DEFAULT_EDITOR_VALUE: Record<SupportedLanguage, string> = {
|
export const TEMP_DEFAULT_EDITOR_VALUE: Record<EditorLanguage, string> = {
|
||||||
c: `/**
|
[EditorLanguage.C]: `/**
|
||||||
* Note: The returned array must be malloced, assume caller calls free().
|
* Note: The returned array must be malloced, assume caller calls free().
|
||||||
*/
|
*/
|
||||||
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
|
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
|
||||||
|
|
||||||
}`,
|
}`,
|
||||||
cpp: `class Solution {
|
[EditorLanguage.CPP]: `class Solution {
|
||||||
public:
|
public:
|
||||||
vector<int> twoSum(vector<int>& nums, int target) {
|
vector<int> twoSum(vector<int>& nums, int target) {
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
import { COriginal, CplusplusOriginal } from 'devicons-react'
|
|
||||||
|
|
||||||
export const SUPPORTED_LANGUAGES = [
|
|
||||||
{
|
|
||||||
id: "c",
|
|
||||||
label: "C",
|
|
||||||
icon: <COriginal size={16} aria-hidden="true" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "cpp",
|
|
||||||
label: "C++",
|
|
||||||
icon: <CplusplusOriginal size={16} aria-hidden="true" />,
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]["id"];
|
|
13
src/hooks/use-monaco-theme.ts
Normal file
13
src/hooks/use-monaco-theme.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { MonacoTheme } from "@/types/monaco-theme";
|
||||||
|
import { MonacoThemeConfig } from "@/config/monaco-theme";
|
||||||
|
|
||||||
|
export function useMonacoTheme() {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
const monacoTheme = resolvedTheme === "light" ? MonacoThemeConfig[MonacoTheme.GitHubLightDefault] : MonacoThemeConfig[MonacoTheme.GitHubDarkDefault];
|
||||||
|
|
||||||
|
return {
|
||||||
|
monacoTheme,
|
||||||
|
};
|
||||||
|
}
|
68
src/lib/language-server.ts
Normal file
68
src/lib/language-server.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import normalizeUrl from "normalize-url";
|
||||||
|
import type { MessageTransports } from "vscode-languageclient";
|
||||||
|
import { EditorLanguageMetadata } from "@/types/editor-language";
|
||||||
|
import type { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
|
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from "vscode-ws-jsonrpc";
|
||||||
|
|
||||||
|
// Create the WebSocket URL based on the protocol and port
|
||||||
|
function createUrl(protocol: string, hostname: string, port: number | null, path: string | null): string {
|
||||||
|
return normalizeUrl(`${protocol}://${hostname}${port ? `:${port}` : ""}${path || ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the language client with the given transports
|
||||||
|
async function createLanguageClient(transports: MessageTransports, lang: EditorLanguageMetadata): Promise<MonacoLanguageClient> {
|
||||||
|
const { MonacoLanguageClient } = await import("monaco-languageclient");
|
||||||
|
const { CloseAction, ErrorAction } = await import("vscode-languageclient");
|
||||||
|
|
||||||
|
return new MonacoLanguageClient({
|
||||||
|
name: `${lang.label} Language Client`,
|
||||||
|
clientOptions: {
|
||||||
|
// use a language id as a document selector
|
||||||
|
documentSelector: [lang.id],
|
||||||
|
// disable the default error handler
|
||||||
|
errorHandler: {
|
||||||
|
error: () => ({ action: ErrorAction.Continue }),
|
||||||
|
closed: () => ({ action: CloseAction.DoNotRestart }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// create a language client connection from the JSON RPC connection on demand
|
||||||
|
connectionProvider: {
|
||||||
|
get: () => {
|
||||||
|
return Promise.resolve(transports);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the WebSocket and create the language client
|
||||||
|
export function connectToLanguageServer(protocol: string, hostname: string, port: number | null, path: string | null, lang: EditorLanguageMetadata): Promise<MonacoLanguageClient> {
|
||||||
|
const url = createUrl(protocol, hostname, port, path);
|
||||||
|
const webSocket = new WebSocket(url);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Handle the WebSocket opening event
|
||||||
|
webSocket.onopen = async () => {
|
||||||
|
const socket = toSocket(webSocket);
|
||||||
|
const reader = new WebSocketMessageReader(socket);
|
||||||
|
const writer = new WebSocketMessageWriter(socket);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const languageClient = await createLanguageClient({ reader, writer }, lang);
|
||||||
|
// Start the language client
|
||||||
|
languageClient.start();
|
||||||
|
|
||||||
|
// Stop the language client when the reader closes
|
||||||
|
reader.onClose(() => languageClient.stop());
|
||||||
|
|
||||||
|
resolve(languageClient);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle WebSocket errors
|
||||||
|
webSocket.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
@ -1,12 +1,23 @@
|
|||||||
|
import { MonacoTheme } from "@/types/monaco-theme";
|
||||||
import { createHighlighter, Highlighter } from "shiki";
|
import { createHighlighter, Highlighter } from "shiki";
|
||||||
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
|
||||||
|
// Get all values from the ProgrammingLanguage and Theme enums
|
||||||
|
const themes = Object.values(MonacoTheme);
|
||||||
|
const languages = Object.values(EditorLanguage);
|
||||||
|
|
||||||
|
// Use lazy initialization for highlighter
|
||||||
let highlighter: Highlighter;
|
let highlighter: Highlighter;
|
||||||
|
|
||||||
async function initializeHighlighter() {
|
async function initializeHighlighter() {
|
||||||
highlighter = await createHighlighter({
|
try {
|
||||||
themes: ["github-light-default", "github-dark-default"],
|
highlighter = await createHighlighter({
|
||||||
langs: ["c"],
|
themes: themes, // Use all values from the Theme enum
|
||||||
});
|
langs: languages, // Use all values from the ProgrammingLanguage enum
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing highlighter:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeHighlighter();
|
initializeHighlighter();
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { clsx, type ClassValue } from "clsx"
|
import { twMerge } from "tailwind-merge";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
|
import LanguageServerConfig from "@/config/language-server";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPath(lang: EditorLanguage): string {
|
||||||
|
const config = LanguageServerConfig[lang];
|
||||||
|
return `file:///${config.lang.fileName}${config.lang.fileExtension}`;
|
||||||
|
}
|
||||||
|
@ -1,58 +1,75 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { type editor } from "monaco-editor";
|
import { getPath } from "@/lib/utils";
|
||||||
import { persist } from "zustand/middleware";
|
import type { editor } from "monaco-editor";
|
||||||
import { JudgeResult } from "@/config/judge";
|
import { JudgeResultMetadata } from "@/types/judge";
|
||||||
import { CODE_EDITOR_OPTIONS } from "@/constants/option";
|
import { EditorLanguage } from "@/types/editor-language";
|
||||||
import { SupportedLanguage } from "@/constants/language";
|
import { createJSONStorage, persist } from "zustand/middleware";
|
||||||
import { MonacoLanguageClient } from "monaco-languageclient";
|
import { LanguageServerMetadata } from "@/types/language-server";
|
||||||
import { DEFAULT_EDITOR_LANGUAGE } from "@/config/editor/language";
|
import { DefaultEditorOptionConfig } from "@/config/editor-option";
|
||||||
|
import { DefaultEditorLanguageConfig } from "@/config/editor-language";
|
||||||
|
|
||||||
interface CodeEditorState {
|
interface CodeEditorState {
|
||||||
editor: editor.IStandaloneCodeEditor | null;
|
|
||||||
language: SupportedLanguage;
|
|
||||||
languageClient: MonacoLanguageClient | null;
|
|
||||||
hydrated: boolean;
|
hydrated: boolean;
|
||||||
result: JudgeResult | null;
|
language: EditorLanguage;
|
||||||
setEditor: (editor: editor.IStandaloneCodeEditor | null) => void;
|
path: string;
|
||||||
setLanguage: (language: SupportedLanguage) => void;
|
value: string;
|
||||||
setLanguageClient: (languageClient: MonacoLanguageClient | null) => void;
|
lspConfig: LanguageServerMetadata | null;
|
||||||
|
isLspEnabled: boolean;
|
||||||
|
editorConfig: editor.IEditorConstructionOptions;
|
||||||
|
editor: editor.IStandaloneCodeEditor | null;
|
||||||
|
result: JudgeResultMetadata | null;
|
||||||
setHydrated: (value: boolean) => void;
|
setHydrated: (value: boolean) => void;
|
||||||
setResult: (result: JudgeResult) => void;
|
setLanguage: (language: EditorLanguage) => void;
|
||||||
|
setPath: (path: string) => void;
|
||||||
|
setValue: (value: string) => void;
|
||||||
|
setLspConfig: (lspConfig: LanguageServerMetadata) => void;
|
||||||
|
setIsLspEnabled: (enabled: boolean) => void;
|
||||||
|
setEditorConfig: (editorConfig: editor.IEditorConstructionOptions) => void;
|
||||||
|
setEditor: (editor: editor.IStandaloneCodeEditor) => void;
|
||||||
|
setResult: (result: JudgeResultMetadata) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCodeEditorStore = create<CodeEditorState>()(
|
export const useCodeEditorStore = create<CodeEditorState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
editor: null,
|
|
||||||
language: DEFAULT_EDITOR_LANGUAGE,
|
|
||||||
languageClient: null,
|
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
|
language: DefaultEditorLanguageConfig.id,
|
||||||
|
path: getPath(DefaultEditorLanguageConfig.id),
|
||||||
|
value: "#include<stdio.h>",
|
||||||
|
lspConfig: null,
|
||||||
|
isLspEnabled: true,
|
||||||
|
editorConfig: DefaultEditorOptionConfig,
|
||||||
|
editor: null,
|
||||||
result: null,
|
result: null,
|
||||||
setEditor: (editor) => set({ editor }),
|
|
||||||
setLanguage: (language) => set({ language }),
|
|
||||||
setLanguageClient: (languageClient) => set({ languageClient }),
|
|
||||||
setHydrated: (value) => set({ hydrated: value }),
|
setHydrated: (value) => set({ hydrated: value }),
|
||||||
|
setLanguage: (language) => set({ language }),
|
||||||
|
setPath: (path) => set({ path }),
|
||||||
|
setValue: (value) => set({ value }),
|
||||||
|
setLspConfig: (lspConfig) => set({ lspConfig }),
|
||||||
|
setIsLspEnabled: (enabled) => set({ isLspEnabled: enabled }),
|
||||||
|
setEditorConfig: (editorConfig) => set({ editorConfig }),
|
||||||
|
setEditor: (editor) => set({ editor: editor }),
|
||||||
setResult: (result) => set({ result }),
|
setResult: (result) => set({ result }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "code-editor-language",
|
name: "code-editor-store",
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
language: state.language,
|
language: state.language,
|
||||||
|
path: state.path,
|
||||||
|
value: state.value,
|
||||||
|
isLspEnabled: state.isLspEnabled,
|
||||||
|
editorConfig: state.editorConfig,
|
||||||
}),
|
}),
|
||||||
onRehydrateStorage: () => (state, error) => {
|
onRehydrateStorage: () => {
|
||||||
if (error) {
|
return (state, error) => {
|
||||||
console.error("hydrate error", error);
|
if (error) {
|
||||||
} else if (state) {
|
console.error("An error happened during hydration", error);
|
||||||
state.setHydrated(true);
|
} else if (state) {
|
||||||
}
|
state.setHydrated(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useCodeEditorOptionStore = create<editor.IEditorConstructionOptions>((set) => ({
|
|
||||||
fontSize: CODE_EDITOR_OPTIONS.fontSize,
|
|
||||||
lineHeight: CODE_EDITOR_OPTIONS.lineHeight,
|
|
||||||
setFontSize: (fontSize: number) => set({ fontSize }),
|
|
||||||
setLineHeight: (lineHeight: number) => set({ lineHeight }),
|
|
||||||
}));
|
|
||||||
|
12
src/types/editor-language.ts
Normal file
12
src/types/editor-language.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export enum EditorLanguage {
|
||||||
|
C = "c",
|
||||||
|
CPP = "cpp",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditorLanguageMetadata = {
|
||||||
|
id: EditorLanguage;
|
||||||
|
label: string;
|
||||||
|
fileName: string;
|
||||||
|
fileExtension: string;
|
||||||
|
icon: React.FunctionComponent<React.SVGProps<SVGElement> & { size?: number | string }>;
|
||||||
|
};
|
35
src/types/judge.ts
Normal file
35
src/types/judge.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { EditorLanguageMetadata } from "./editor-language";
|
||||||
|
|
||||||
|
// Result type definitions
|
||||||
|
export enum ExitCode {
|
||||||
|
SE = 0, // System Error
|
||||||
|
CS = 1, // Compilation Success
|
||||||
|
CE = 2, // Compilation Error
|
||||||
|
TLE = 3, // Time Limit Exceeded
|
||||||
|
MLE = 4, // Memory Limit Exceeded
|
||||||
|
RE = 5, // Runtime Error
|
||||||
|
AC = 6, // Accepted
|
||||||
|
WA = 7, // Wrong Answer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JudgeResultMetadata = {
|
||||||
|
output: string;
|
||||||
|
exitCode: ExitCode;
|
||||||
|
executionTime?: number;
|
||||||
|
memoryUsage?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JudgeMetadata = {
|
||||||
|
editorLanguageMetadata: EditorLanguageMetadata;
|
||||||
|
dockerMetadata: DockerMetadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DockerMetadata = {
|
||||||
|
image: string;
|
||||||
|
tag: string;
|
||||||
|
workingDir: string;
|
||||||
|
timeLimit: number;
|
||||||
|
memoryLimit: number;
|
||||||
|
compileOutputLimit: number;
|
||||||
|
runOutputLimit: number;
|
||||||
|
}
|
9
src/types/language-server.ts
Normal file
9
src/types/language-server.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { EditorLanguageMetadata } from "./editor-language";
|
||||||
|
|
||||||
|
export type LanguageServerMetadata = {
|
||||||
|
protocol: string;
|
||||||
|
hostname: string;
|
||||||
|
port: number | null;
|
||||||
|
path: string | null;
|
||||||
|
lang: EditorLanguageMetadata;
|
||||||
|
};
|
9
src/types/monaco-theme.ts
Normal file
9
src/types/monaco-theme.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum MonacoTheme {
|
||||||
|
GitHubLightDefault = "github-light-default",
|
||||||
|
GitHubDarkDefault = "github-dark-default",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonacoThemeMetadata = {
|
||||||
|
id: MonacoTheme;
|
||||||
|
label: string;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user