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
|
||||
.github
|
||||
.next
|
||||
node_modules
|
||||
.dockerignore
|
||||
.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; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
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 && \
|
||||
curl -fsSL https://bun.sh/install | bash && \
|
||||
export BUN_INSTALL="$HOME/.bun" && \
|
||||
@ -25,7 +25,6 @@ RUN \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
@ -41,7 +40,7 @@ RUN \
|
||||
if [ -f yarn.lock ]; then yarn 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 bun.lock ]; then \
|
||||
elif [ -f bun.lock ] || [ -f bun.lockb ]; then \
|
||||
apk add --no-cache curl bash && \
|
||||
curl -fsSL https://bun.sh/install | bash && \
|
||||
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.
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# 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/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
USER root
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
@ -1,74 +0,0 @@
|
||||
# syntax=docker.io/docker/dockerfile:1
|
||||
FROM dockerp.com/node:22-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories && \
|
||||
apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* bun.lock* .npmrc* ./
|
||||
|
||||
# 统一配置所有包管理器使用淘宝源
|
||||
RUN \
|
||||
npm config set registry https://registry.npmmirror.com && \
|
||||
if [ -f yarn.lock ]; then \
|
||||
# yarn config set registry https://registry.npmmirror.com && \
|
||||
yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then \
|
||||
npm ci --registry=https://registry.npmmirror.com; \
|
||||
elif [ -f pnpm-lock.yaml ]; then \
|
||||
corepack enable pnpm && \
|
||||
# pnpm config set registry https://registry.npmmirror.com && \
|
||||
pnpm i --frozen-lockfile; \
|
||||
elif [ -f bun.lock ]; then \
|
||||
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories && \
|
||||
apk add --no-cache curl bash && \
|
||||
npm install -g bun && \
|
||||
export BUN_INSTALL="$HOME/.bun" && \
|
||||
export PATH="$BUN_INSTALL/bin:$PATH" && \
|
||||
bun install --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# 合并构建命令
|
||||
RUN \
|
||||
npm config set registry https://registry.npmmirror.com && \
|
||||
if [ -f yarn.lock ]; then yarn 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 bun.lock ]; then \
|
||||
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories && \
|
||||
apk add --no-cache curl bash && \
|
||||
npm install -g bun && \
|
||||
export BUN_INSTALL="$HOME/.bun" && \
|
||||
export PATH="$BUN_INSTALL/bin:$PATH" && \
|
||||
bun run build; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories && \
|
||||
apk add --no-cache curl && \
|
||||
addgroup --system --gid 1001 nodejs && \
|
||||
adduser --system --uid 1001 nextjs
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000 HOSTNAME="0.0.0.0"
|
||||
CMD ["node", "server.js"]
|
41
README.md
41
README.md
@ -26,29 +26,46 @@ Complete these steps before launching the editor for seamless LSP integration!
|
||||
|
||||
### 🐳 Docker Deployment (Recommended)
|
||||
|
||||
Deploy the project quickly using pre-built Docker images:
|
||||
|
||||
```sh
|
||||
# Clone repository
|
||||
# Clone the repository
|
||||
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
||||
cd monaco-editor-lsp-next
|
||||
|
||||
# Build and launch containers
|
||||
docker compose up -d --build
|
||||
# Start the application with pre-built images
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 🔨 Manual Installation
|
||||
### 🔨 Local Manual Build
|
||||
|
||||
Build the images locally and start the containers:
|
||||
|
||||
```sh
|
||||
# Clone repository
|
||||
# Clone the repository
|
||||
git clone https://github.com/cfngc4594/monaco-editor-lsp-next
|
||||
cd monaco-editor-lsp-next
|
||||
|
||||
# Start core LSP containers
|
||||
docker compose up -d --build lsp-c lsp-cpp
|
||||
# Build and start containers using the local configuration
|
||||
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
|
||||
|
||||
# Launch development server
|
||||
bun run dev
|
||||
```
|
||||
|
||||
@ -97,10 +114,12 @@ bun run dev
|
||||
Syntax highlighting depends on `rehype-pretty-code`'s deprecated `getHighlighter` API from `shiki@legacy`.
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- **Affected File:** `src/components/mdx-preview.tsx`
|
||||
- **Dependency Chain:** `rehype-pretty-code` → `shiki@legacy`
|
||||
- **Version Constraints**:
|
||||
```bash
|
||||
|
||||
```sh
|
||||
"shiki": "<=2.5.0"
|
||||
"@shikijs/monaco": "<=2.5.0"
|
||||
```
|
||||
|
25
bun.lock
25
bun.lock
@ -16,7 +16,6 @@
|
||||
"@radix-ui/react-toggle": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@types/vscode": "^1.97.0",
|
||||
"bun": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"devicons-react": "^1.4.0",
|
||||
@ -199,28 +198,6 @@
|
||||
|
||||
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "https://registry.npmmirror.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
||||
|
||||
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xBz/Q7X6AFwMg7MXtBemjjt5uB+tvEYBmi9Zbm1r8qnI2V8m/Smuhma0EARhiVfLuIAYj2EM5qjzxeAFV4TBJA=="],
|
||||
|
||||
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ufyty+2754QCFDhq447H39JiqabMlFRItLn1YFp+2hdpKak7KCYLGOUuHnlr1pmImKJzDHURjnvTTq1QRlUWAA=="],
|
||||
|
||||
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-stsq8vBiYgfGunBGlf2M7ST7Ymyw3WnwrxEeJ04vkKmMEEE2LpX8Rkol6UPRvZawab9s9/scFIRgFi6hu9H4SQ=="],
|
||||
|
||||
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-OhVpzme2vvLA7w8GYeJg2SQ2h2CwJQN9oYDiGeoML4EwE+DEoYHdxgaBZsQySOwZtFIr8ufpc/8iD4hssJ50qg=="],
|
||||
|
||||
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-+lxWF7up9MuB1ZdGxXCH3AH3XmYtdBC6soQ38+yg3+y3iOPrAlSG+wytHEkypN/UU2mGvCuaEED3cMvejrGdDw=="],
|
||||
|
||||
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-oof3ii92Cz2yIOZRbVFHMHmffCutRPFITIdXLZ2/rkqVuKUe0ZdqWjHPhxJFm31AL9MlJ/dSqDbPb51SaLI7tw=="],
|
||||
|
||||
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-3nmDDZJH73MzhBg2sRYioj4CE8wgaz0w24OieMqj4/c44BbNr3X5RewrldsMD2cU6DtVbi52FuD5WpTw3N8nmw=="],
|
||||
|
||||
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-cLdMbK7srNoUbYSG3Tp4GYdPAO0+5mgUhdbU053GZs0DLQmQ8h1JQhALp+ZjrUWstmQe7ddcNu7l7EAu6E76XA=="],
|
||||
|
||||
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-qDsUvKCW0WUlVOt6Yx5eEzxgCbvzuSJBsu0sXtr6uGt8TnMKghmliRO5FmiMLQ0k/PUDA8vPJCtuv5k14bDi6g=="],
|
||||
|
||||
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-4YRJd4pdaTWEM+uawYmchOeNv874RGAFpIZQubWnN4SBf6HfcDm0OMMZcm0f0I70Wd5gbPg1+rvCRtDZWVmZog=="],
|
||||
|
||||
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-j/G4bnfsRAiCvpTADda40m29iSGvueIaF9Kc9hBu4jN8dTS9fEXdNNXuf8c30/z7/npxw2dhzsAn8jbc5QvD1A=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
@ -471,8 +448,6 @@
|
||||
|
||||
"buildcheck": ["buildcheck@0.0.6", "https://registry.npmmirror.com/buildcheck/-/buildcheck-0.0.6.tgz", {}, "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A=="],
|
||||
|
||||
"bun": ["bun@1.2.4", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.2.4", "@oven/bun-darwin-x64": "1.2.4", "@oven/bun-darwin-x64-baseline": "1.2.4", "@oven/bun-linux-aarch64": "1.2.4", "@oven/bun-linux-aarch64-musl": "1.2.4", "@oven/bun-linux-x64": "1.2.4", "@oven/bun-linux-x64-baseline": "1.2.4", "@oven/bun-linux-x64-musl": "1.2.4", "@oven/bun-linux-x64-musl-baseline": "1.2.4", "@oven/bun-windows-x64": "1.2.4", "@oven/bun-windows-x64-baseline": "1.2.4" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bun.exe" } }, "sha512-ZY0EZ/UKqheaLeAtMsfJA6jWoWvV9HAtfFaOJSmS3LrNpFKs1Sg5fZLSsczN1h3a+Dtheo4O3p3ZYWrf40kRGw=="],
|
||||
|
||||
"busboy": ["busboy@1.6.0", "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
@ -2,7 +2,7 @@ services:
|
||||
monaco-editor-lsp-next:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile.cn
|
||||
dockerfile: Dockerfile
|
||||
image: monaco-editor-lsp-next:latest
|
||||
container_name: monaco-editor-lsp-next
|
||||
restart: always
|
||||
@ -19,14 +19,13 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
user: root
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
lsp-c:
|
||||
build:
|
||||
context: ./docker/lsp/clangd
|
||||
dockerfile: Dockerfile.cn
|
||||
dockerfile: Dockerfile
|
||||
image: lsp-c:latest
|
||||
container_name: lsp-c
|
||||
restart: always
|
||||
@ -43,7 +42,7 @@ services:
|
||||
lsp-cpp:
|
||||
build:
|
||||
context: ./docker/lsp/clangd
|
||||
dockerfile: Dockerfile.cn
|
||||
dockerfile: Dockerfile
|
||||
image: lsp-cpp:latest
|
||||
container_name: lsp-cpp
|
||||
restart: always
|
16
compose.yml
16
compose.yml
@ -1,9 +1,6 @@
|
||||
services:
|
||||
monaco-editor-lsp-next:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
image: monaco-editor-lsp-next:latest
|
||||
image: cfngc4594/monaco-editor-lsp-next:latest
|
||||
container_name: monaco-editor-lsp-next
|
||||
restart: always
|
||||
ports:
|
||||
@ -19,15 +16,11 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
user: root
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
lsp-c:
|
||||
build:
|
||||
context: ./docker/lsp/clangd
|
||||
dockerfile: Dockerfile
|
||||
image: lsp-c:latest
|
||||
image: cfngc4594/lsp-c:latest
|
||||
container_name: lsp-c
|
||||
restart: always
|
||||
ports:
|
||||
@ -41,10 +34,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
lsp-cpp:
|
||||
build:
|
||||
context: ./docker/lsp/clangd
|
||||
dockerfile: Dockerfile
|
||||
image: lsp-cpp:latest
|
||||
image: cfngc4594/lsp-cpp:latest
|
||||
container_name: lsp-cpp
|
||||
restart: always
|
||||
ports:
|
||||
|
BIN
demo.png
BIN
demo.png
Binary file not shown.
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 243 KiB |
@ -1,31 +0,0 @@
|
||||
FROM dockerp.com/alpine:latest AS builder
|
||||
|
||||
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories&& \
|
||||
apk add --no-cache git npm
|
||||
|
||||
# 修改为南京大学镜像源
|
||||
RUN npm config set registry https://repo.nju.edu.cn/repository/npm/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN git clone https://gh-proxy.com/github.com/wylieconlon/jsonrpc-ws-proxy.git
|
||||
|
||||
WORKDIR /app/jsonrpc-ws-proxy
|
||||
|
||||
COPY servers.yml .
|
||||
|
||||
# 合并命令减少镜像层
|
||||
RUN npm install --registry=https://repo.nju.edu.cn/repository/npm/ && npm run prepare
|
||||
|
||||
FROM dockerp.com/alpine:latest
|
||||
|
||||
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirror.nju.edu.cn/alpine#g' /etc/apk/repositories&& \
|
||||
apk add --no-cache build-base clang-extra-tools nodejs
|
||||
|
||||
WORKDIR /app/jsonrpc-ws-proxy
|
||||
|
||||
COPY --from=builder /app/jsonrpc-ws-proxy .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "dist/server.js", "--port", "3000", "--languageServers", "servers.yml"]
|
@ -21,7 +21,6 @@
|
||||
"@radix-ui/react-toggle": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@types/vscode": "^1.97.0",
|
||||
"bun": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"devicons-react": "^1.4.0",
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import LanguageSelector from "./language-selector";
|
||||
import FormatButton from "./format-button";
|
||||
import CopyButton from "./copy-button";
|
||||
import RedoButton from "./redo-button";
|
||||
import UndoButton from "./undo-button";
|
||||
import ResetButton from "./reset-button";
|
||||
import FormatButton from "./format-button";
|
||||
import LanguageSelector from "./language-selector";
|
||||
|
||||
interface WorkspaceEditorHeaderProps {
|
||||
className?: string;
|
||||
|
@ -7,28 +7,37 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { getPath } from "@/lib/utils";
|
||||
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 { SUPPORTED_LANGUAGES } from "@/constants/language";
|
||||
|
||||
export default function LanguageSelector() {
|
||||
const { hydrated, language, setLanguage } = useCodeEditorStore();
|
||||
const { hydrated, language, setLanguage, setPath, setLspConfig } = useCodeEditorStore();
|
||||
|
||||
if (!hydrated) {
|
||||
return <Skeleton className="h-6 w-16 rounded-2xl" />;
|
||||
}
|
||||
|
||||
const handleValueChange = (lang: EditorLanguage) => {
|
||||
setLanguage(lang);
|
||||
setPath(getPath(lang));
|
||||
setLspConfig(LanguageServerConfig[lang]);
|
||||
};
|
||||
|
||||
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">
|
||||
<SelectValue placeholder="Select language" />
|
||||
</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">
|
||||
{SUPPORTED_LANGUAGES.map((lang) => (
|
||||
<SelectItem key={lang.id} value={lang.id}>
|
||||
{lang.icon}
|
||||
{Object.values(EditorLanguageConfig).map((langConfig) => (
|
||||
<SelectItem key={langConfig.id} value={langConfig.id}>
|
||||
<langConfig.icon size={16} aria-hidden="true" />
|
||||
<span className="truncate text-sm font-semibold mr-2">
|
||||
{lang.label}
|
||||
{langConfig.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} 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() {
|
||||
const { editor, language } = useCodeEditorStore();
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
@ -24,7 +23,7 @@ export default function ResetButton() {
|
||||
aria-label="Reset Code"
|
||||
onClick={() => {
|
||||
if (editor) {
|
||||
const value = DEFAULT_EDITOR_VALUE[language];
|
||||
const value = TEMP_DEFAULT_EDITOR_VALUE[language];
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
const fullRange = model.getFullModelRange();
|
||||
|
@ -3,7 +3,9 @@
|
||||
import tar from "tar-stream";
|
||||
import Docker from "dockerode";
|
||||
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
|
||||
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
||||
@ -41,10 +43,11 @@ function createTarStream(file: string, value: string) {
|
||||
}
|
||||
|
||||
export async function judge(
|
||||
language: string,
|
||||
language: EditorLanguage,
|
||||
value: string
|
||||
): Promise<JudgeResult> {
|
||||
const { fileName, fileExtension, image, tag, workingDir, memoryLimit, timeLimit, compileOutputLimit, runOutputLimit } = LanguageConfigs[language];
|
||||
): Promise<JudgeResultMetadata> {
|
||||
const { fileName, fileExtension } = JudgeConfig[language].editorLanguageMetadata;
|
||||
const { image, tag, workingDir, memoryLimit, timeLimit, compileOutputLimit, runOutputLimit } = JudgeConfig[language].dockerMetadata;
|
||||
const file = `${fileName}.${fileExtension}`;
|
||||
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({
|
||||
Cmd: ["gcc", "-O2", file, "-o", fileName],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
return new Promise<JudgeResult>((resolve, reject) => {
|
||||
return new Promise<JudgeResultMetadata>((resolve, reject) => {
|
||||
compileExec.start({}, (error, stream) => {
|
||||
if (error || !stream) {
|
||||
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 exitCode = (await compileExec.inspect()).ExitCode;
|
||||
|
||||
let result: JudgeResult;
|
||||
let result: JudgeResultMetadata;
|
||||
|
||||
if (exitCode !== 0 || stderr) {
|
||||
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
|
||||
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({
|
||||
Cmd: [`./${fileName}`],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
return new Promise<JudgeResult>((resolve, reject) => {
|
||||
return new Promise<JudgeResultMetadata>((resolve, reject) => {
|
||||
const stdoutChunks: string[] = [];
|
||||
let stdoutLength = 0;
|
||||
const stdoutStream = new Writable({
|
||||
@ -211,7 +214,7 @@ async function run(container: Docker.Container, fileName: string, timeLimit?: nu
|
||||
const stderr = stderrChunks.join("");
|
||||
const exitCode = (await runExec.inspect()).ExitCode;
|
||||
|
||||
let result: JudgeResult;
|
||||
let result: JudgeResultMetadata;
|
||||
|
||||
// Exit code 0 means successful execution
|
||||
if (exitCode === 0) {
|
||||
|
@ -1,240 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
toSocket,
|
||||
WebSocketMessageReader,
|
||||
WebSocketMessageWriter,
|
||||
} from "vscode-ws-jsonrpc";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useTheme } from "next-themes";
|
||||
import normalizeUrl from "normalize-url";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
import { highlighter } from "@/lib/shiki";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { editor } from "monaco-editor";
|
||||
import { shikiToMonaco } from "@shikijs/monaco";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { CODE_EDITOR_OPTIONS } from "@/constants/option";
|
||||
import { DEFAULT_EDITOR_PATH } from "@/config/editor/path";
|
||||
import { DEFAULT_EDITOR_VALUE } from "@/config/editor/value";
|
||||
import type { Monaco } from "@monaco-editor/react";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
||||
import LanguageServerConfig from "@/config/language-server";
|
||||
import { connectToLanguageServer } from "@/lib/language-server";
|
||||
import { useCodeEditorStore } from "@/store/useCodeEditorStore";
|
||||
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(
|
||||
async () => {
|
||||
await import("vscode");
|
||||
|
||||
const monaco = await import("monaco-editor");
|
||||
const { loader } = await import("@monaco-editor/react");
|
||||
loader.config({ monaco });
|
||||
|
||||
return (await import("@monaco-editor/react")).Editor;
|
||||
},
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="h-full w-full p-4">
|
||||
<Skeleton className="h-full w-full rounded-3xl" />
|
||||
</div>
|
||||
),
|
||||
loading: () => <CodeEditorLoadingSkeleton />,
|
||||
}
|
||||
);
|
||||
|
||||
type ConnectionHandle = {
|
||||
client: MonacoLanguageClient | null;
|
||||
socket: WebSocket | null;
|
||||
controller: AbortController;
|
||||
};
|
||||
|
||||
export default function CodeEditor() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const connectionRef = useRef<ConnectionHandle>({
|
||||
client: null,
|
||||
socket: null,
|
||||
controller: new AbortController(),
|
||||
});
|
||||
const { fontSize, lineHeight } = useCodeEditorOptionStore();
|
||||
const { language, setEditor } = useCodeEditorStore();
|
||||
const {
|
||||
hydrated,
|
||||
language,
|
||||
path,
|
||||
value,
|
||||
editorConfig,
|
||||
isLspEnabled,
|
||||
setEditor,
|
||||
} = useCodeEditorStore();
|
||||
const { monacoTheme } = useMonacoTheme();
|
||||
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||
const monacoLanguageClientRef = useRef<MonacoLanguageClient | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentHandle: ConnectionHandle = {
|
||||
client: null,
|
||||
socket: null,
|
||||
controller: new AbortController(),
|
||||
};
|
||||
const signal = currentHandle.controller.signal;
|
||||
connectionRef.current = currentHandle;
|
||||
// Connect to LSP only if enabled
|
||||
const connectLSP = useCallback(async () => {
|
||||
if (!(isLspEnabled && language && editorRef.current)) return;
|
||||
|
||||
const cleanupConnection = async (handle: ConnectionHandle) => {
|
||||
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;
|
||||
}
|
||||
const lspConfig = LanguageServerConfig[language];
|
||||
|
||||
// Cleanup WebSocket
|
||||
if (handle.socket) {
|
||||
console.log("Closing WebSocket...");
|
||||
const socket = handle.socket;
|
||||
socket.onopen = null;
|
||||
socket.onerror = null;
|
||||
socket.onclose = null;
|
||||
socket.onmessage = null;
|
||||
if (!lspConfig) return;
|
||||
|
||||
try {
|
||||
if (
|
||||
[WebSocket.OPEN, WebSocket.CONNECTING].includes(
|
||||
socket.readyState as WebSocket["OPEN"] | WebSocket["CONNECTING"]
|
||||
)
|
||||
) {
|
||||
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 ?? "");
|
||||
// If there's an existing language client, stop it first
|
||||
if (monacoLanguageClientRef.current) {
|
||||
monacoLanguageClientRef.current.stop();
|
||||
monacoLanguageClientRef.current = null;
|
||||
}
|
||||
|
||||
// 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 =
|
||||
typeof window !== "undefined"
|
||||
? localStorage.getItem(`code-editor-value-${language}`) ||
|
||||
DEFAULT_EDITOR_VALUE[language]
|
||||
: DEFAULT_EDITOR_VALUE[language];
|
||||
function handleEditorWillMount(monaco: Monaco) {
|
||||
shikiToMonaco(highlighter, monaco);
|
||||
}
|
||||
|
||||
return (
|
||||
<Editor
|
||||
defaultLanguage={language}
|
||||
value={editorValue}
|
||||
path={DEFAULT_EDITOR_PATH[language]}
|
||||
theme={resolvedTheme === "light" ? "github-light-default" : "github-dark-default"}
|
||||
className="h-full"
|
||||
options={mergeOptions}
|
||||
beforeMount={(monaco) => {
|
||||
shikiToMonaco(highlighter, monaco);
|
||||
}}
|
||||
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>
|
||||
}
|
||||
language={language}
|
||||
theme={monacoTheme.id}
|
||||
path={path}
|
||||
value={value}
|
||||
beforeMount={handleEditorWillMount}
|
||||
onMount={handleEditorDidMount}
|
||||
options={editorConfig}
|
||||
loading={<CodeEditorLoadingSkeleton />}
|
||||
className="h-full w-full py-2"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CodeBlockWithCopyProps {
|
||||
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";
|
||||
|
||||
export const CODE_EDITOR_OPTIONS: editor.IEditorConstructionOptions = {
|
||||
export const DefaultEditorOptionConfig: editor.IEditorConstructionOptions = {
|
||||
autoIndent: "full",
|
||||
automaticLayout: true,
|
||||
contextmenu: true,
|
||||
@ -15,16 +15,13 @@ export const CODE_EDITOR_OPTIONS: editor.IEditorConstructionOptions = {
|
||||
lineHeight: 20,
|
||||
matchBrackets: "always",
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
padding: {
|
||||
top: 8
|
||||
enabled: false,
|
||||
},
|
||||
readOnly: false,
|
||||
scrollbar: {
|
||||
horizontalSliderSize: 10,
|
||||
verticalSliderSize: 10
|
||||
verticalSliderSize: 10,
|
||||
},
|
||||
showFoldingControls: "always",
|
||||
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
|
||||
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
|
||||
}
|
||||
import { EditorLanguage } from "@/types/editor-language";
|
||||
import { EditorLanguageConfig } from "./editor-language";
|
||||
import { DockerMetadata, JudgeMetadata } from "@/types/judge";
|
||||
|
||||
export type JudgeResult = {
|
||||
output: string;
|
||||
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",
|
||||
export const DockerConfig: Record<EditorLanguage, DockerMetadata> = {
|
||||
[EditorLanguage.C]: {
|
||||
image: "gcc",
|
||||
tag: "latest",
|
||||
workingDir: "/src",
|
||||
@ -45,11 +12,7 @@ export const LanguageConfigs: Record<string, LanguageConfig> = {
|
||||
compileOutputLimit: 1 * 1024 * 1024,
|
||||
runOutputLimit: 1 * 1024 * 1024,
|
||||
},
|
||||
cpp: {
|
||||
id: "cpp",
|
||||
label: "C++",
|
||||
fileName: "main",
|
||||
fileExtension: "cpp",
|
||||
[EditorLanguage.CPP]: {
|
||||
image: "gcc",
|
||||
tag: "latest",
|
||||
workingDir: "/src",
|
||||
@ -57,5 +20,16 @@ export const LanguageConfigs: Record<string, LanguageConfig> = {
|
||||
memoryLimit: 128,
|
||||
compileOutputLimit: 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 {
|
||||
id: SupportedLanguage
|
||||
label: string
|
||||
hostname: string
|
||||
protocol: string
|
||||
port: number | null
|
||||
path: string | null
|
||||
}
|
||||
|
||||
export const SUPPORTED_LANGUAGE_SERVERS: LanguageServerConfig[] = [
|
||||
{
|
||||
id: "c",
|
||||
label: "C",
|
||||
const LanguageServerConfig: Record<EditorLanguage, LanguageServerMetadata> = {
|
||||
[EditorLanguage.C]: {
|
||||
protocol: process.env.NEXT_PUBLIC_LSP_C_PROTOCOL || "ws",
|
||||
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,
|
||||
path: process.env.NEXT_PUBLIC_LSP_C_PATH || "/clangd",
|
||||
lang: EditorLanguageConfig[EditorLanguage.C],
|
||||
},
|
||||
{
|
||||
id: "cpp",
|
||||
label: "C++",
|
||||
[EditorLanguage.CPP]: {
|
||||
protocol: process.env.NEXT_PUBLIC_LSP_CPP_PROTOCOL || "ws",
|
||||
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,
|
||||
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> = {
|
||||
c: `/**
|
||||
export const TEMP_DEFAULT_EDITOR_VALUE: Record<EditorLanguage, string> = {
|
||||
[EditorLanguage.C]: `/**
|
||||
* Note: The returned array must be malloced, assume caller calls free().
|
||||
*/
|
||||
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
|
||||
|
||||
}`,
|
||||
cpp: `class Solution {
|
||||
[EditorLanguage.CPP]: `class Solution {
|
||||
public:
|
||||
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 { 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;
|
||||
|
||||
async function initializeHighlighter() {
|
||||
highlighter = await createHighlighter({
|
||||
themes: ["github-light-default", "github-dark-default"],
|
||||
langs: ["c"],
|
||||
});
|
||||
try {
|
||||
highlighter = await createHighlighter({
|
||||
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();
|
||||
|
@ -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[]) {
|
||||
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 { type editor } from "monaco-editor";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { JudgeResult } from "@/config/judge";
|
||||
import { CODE_EDITOR_OPTIONS } from "@/constants/option";
|
||||
import { SupportedLanguage } from "@/constants/language";
|
||||
import { MonacoLanguageClient } from "monaco-languageclient";
|
||||
import { DEFAULT_EDITOR_LANGUAGE } from "@/config/editor/language";
|
||||
import { getPath } from "@/lib/utils";
|
||||
import type { editor } from "monaco-editor";
|
||||
import { JudgeResultMetadata } from "@/types/judge";
|
||||
import { EditorLanguage } from "@/types/editor-language";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import { LanguageServerMetadata } from "@/types/language-server";
|
||||
import { DefaultEditorOptionConfig } from "@/config/editor-option";
|
||||
import { DefaultEditorLanguageConfig } from "@/config/editor-language";
|
||||
|
||||
interface CodeEditorState {
|
||||
editor: editor.IStandaloneCodeEditor | null;
|
||||
language: SupportedLanguage;
|
||||
languageClient: MonacoLanguageClient | null;
|
||||
hydrated: boolean;
|
||||
result: JudgeResult | null;
|
||||
setEditor: (editor: editor.IStandaloneCodeEditor | null) => void;
|
||||
setLanguage: (language: SupportedLanguage) => void;
|
||||
setLanguageClient: (languageClient: MonacoLanguageClient | null) => void;
|
||||
language: EditorLanguage;
|
||||
path: string;
|
||||
value: string;
|
||||
lspConfig: LanguageServerMetadata | null;
|
||||
isLspEnabled: boolean;
|
||||
editorConfig: editor.IEditorConstructionOptions;
|
||||
editor: editor.IStandaloneCodeEditor | null;
|
||||
result: JudgeResultMetadata | null;
|
||||
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>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
editor: null,
|
||||
language: DEFAULT_EDITOR_LANGUAGE,
|
||||
languageClient: null,
|
||||
hydrated: false,
|
||||
language: DefaultEditorLanguageConfig.id,
|
||||
path: getPath(DefaultEditorLanguageConfig.id),
|
||||
value: "#include<stdio.h>",
|
||||
lspConfig: null,
|
||||
isLspEnabled: true,
|
||||
editorConfig: DefaultEditorOptionConfig,
|
||||
editor: null,
|
||||
result: null,
|
||||
setEditor: (editor) => set({ editor }),
|
||||
setLanguage: (language) => set({ language }),
|
||||
setLanguageClient: (languageClient) => set({ languageClient }),
|
||||
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 }),
|
||||
}),
|
||||
{
|
||||
name: "code-editor-language",
|
||||
name: "code-editor-store",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
language: state.language,
|
||||
path: state.path,
|
||||
value: state.value,
|
||||
isLspEnabled: state.isLspEnabled,
|
||||
editorConfig: state.editorConfig,
|
||||
}),
|
||||
onRehydrateStorage: () => (state, error) => {
|
||||
if (error) {
|
||||
console.error("hydrate error", error);
|
||||
} else if (state) {
|
||||
state.setHydrated(true);
|
||||
}
|
||||
onRehydrateStorage: () => {
|
||||
return (state, error) => {
|
||||
if (error) {
|
||||
console.error("An error happened during hydration", error);
|
||||
} 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