Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0a2010985 | ||
|
|
1afa7a1a5d | ||
|
|
a9ddf03e93 |
+2
-1
@@ -2,6 +2,7 @@
|
|||||||
"extends": ["next/core-web-vitals", "next/typescript"],
|
"extends": ["next/core-web-vitals", "next/typescript"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-unused-vars": "warn",
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
"@typescript-eslint/no-empty-object-type": "warn"
|
"@typescript-eslint/no-empty-object-type": "warn",
|
||||||
|
"@typescript-eslint/no-require-imports": "warn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
},
|
},
|
||||||
"ligaturesCheckbox": {
|
"ligaturesCheckbox": {
|
||||||
"label": "Font Ligatures"
|
"label": "Font Ligatures"
|
||||||
|
},
|
||||||
|
"minimapCheckbox": {
|
||||||
|
"label": "Open Minimap"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
},
|
},
|
||||||
"ligaturesCheckbox": {
|
"ligaturesCheckbox": {
|
||||||
"label": "连字"
|
"label": "连字"
|
||||||
|
},
|
||||||
|
"minimapCheckbox": {
|
||||||
|
"label": "打开缩略图"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,18 @@
|
|||||||
"gitea-js": "^1.22.0",
|
"gitea-js": "^1.22.0",
|
||||||
"jotai": "^2.10.3",
|
"jotai": "^2.10.3",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
|
"monaco-languageclient": "^8.8.3",
|
||||||
"next": "15.0.4",
|
"next": "15.0.4",
|
||||||
"next-auth": "^5.0.0-beta.25",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"next-intl": "^3.26.1",
|
"next-intl": "^3.26.1",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
|
"normalize-url": "^8.0.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vscode-ws-jsonrpc": "^3.3.2",
|
||||||
"zustand": "^5.0.2"
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
toSocket,
|
||||||
|
WebSocketMessageReader,
|
||||||
|
WebSocketMessageWriter,
|
||||||
|
} from "vscode-ws-jsonrpc";
|
||||||
|
import {
|
||||||
|
CloseAction,
|
||||||
|
ErrorAction,
|
||||||
|
MessageTransports,
|
||||||
|
} from "vscode-languageclient";
|
||||||
import "@fontsource-variable/fira-code";
|
import "@fontsource-variable/fira-code";
|
||||||
|
import normalizeUrl from "normalize-url";
|
||||||
import { createHighlighter } from "shiki";
|
import { createHighlighter } from "shiki";
|
||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
import { shikiToMonaco } from "@shikijs/monaco";
|
import { shikiToMonaco } from "@shikijs/monaco";
|
||||||
import { useEffect, useRef, useMemo } from "react";
|
import { useEffect, useRef, useMemo } from "react";
|
||||||
import { useCodeEditorStore } from "@/store/codeEditorStore";
|
import { useCodeEditorStore } from "@/store/codeEditorStore";
|
||||||
|
import { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
import MonacoEditor, { type Monaco } from "@monaco-editor/react";
|
import MonacoEditor, { type Monaco } from "@monaco-editor/react";
|
||||||
|
import { initServices } from "monaco-languageclient/vscode/services";
|
||||||
|
|
||||||
|
const ReconnectingWebSocket = require("reconnecting-websocket");
|
||||||
|
|
||||||
const ADDITIONAL_THEMES = [
|
const ADDITIONAL_THEMES = [
|
||||||
"andromeeda",
|
"andromeeda",
|
||||||
@@ -26,14 +41,17 @@ const ADDITIONAL_THEMES = [
|
|||||||
|
|
||||||
const ADDITIONAL_LANGUAGES = [
|
const ADDITIONAL_LANGUAGES = [
|
||||||
"c",
|
"c",
|
||||||
|
"cpp",
|
||||||
"java",
|
"java",
|
||||||
"python",
|
"python",
|
||||||
|
"typescript",
|
||||||
] as const satisfies Parameters<typeof createHighlighter>[0]["langs"];
|
] as const satisfies Parameters<typeof createHighlighter>[0]["langs"];
|
||||||
|
|
||||||
export function CodeEditor() {
|
export function CodeEditor() {
|
||||||
const monacoRef = useRef<Monaco | null>(null);
|
const monacoRef = useRef<Monaco | null>(null);
|
||||||
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
const { lang, path, theme, value, isLigature } = useCodeEditorStore();
|
const { lang, path, theme, value, isMinimap, isLigature } =
|
||||||
|
useCodeEditorStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (monacoRef.current && editorRef.current) {
|
if (monacoRef.current && editorRef.current) {
|
||||||
@@ -42,22 +60,84 @@ export function CodeEditor() {
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() => ({
|
() =>
|
||||||
minimap: { enabled: false },
|
({
|
||||||
|
minimap: { enabled: isMinimap, size: "fit" },
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: "Fira Code Variable, monospace",
|
fontFamily: "Fira Code Variable, monospace",
|
||||||
tabSize: 4,
|
tabSize: 4,
|
||||||
showFoldingControls: "always" as const,
|
showFoldingControls: "always",
|
||||||
fontLigatures: isLigature,
|
fontLigatures: isLigature,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
|
autoIndent: "full",
|
||||||
guides: {
|
guides: {
|
||||||
bracketPairs: true,
|
bracketPairs: true,
|
||||||
indentation: true,
|
indentation: true,
|
||||||
},
|
},
|
||||||
}),
|
} as editor.IStandaloneEditorConstructionOptions),
|
||||||
[isLigature]
|
[isMinimap, isLigature]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const createLanguageClient = (
|
||||||
|
transports: MessageTransports
|
||||||
|
): MonacoLanguageClient => {
|
||||||
|
return new MonacoLanguageClient({
|
||||||
|
name: "LSP Language Client",
|
||||||
|
clientOptions: {
|
||||||
|
documentSelector: ADDITIONAL_LANGUAGES,
|
||||||
|
errorHandler: {
|
||||||
|
error: () => ({ action: ErrorAction.Continue }),
|
||||||
|
closed: () => ({ action: CloseAction.DoNotRestart }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connectionProvider: {
|
||||||
|
get: () => {
|
||||||
|
return Promise.resolve(transports);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monacoRef.current) return;
|
||||||
|
const url = normalizeUrl(`ws://172.20.0.13:3000`);
|
||||||
|
const socketOptions = {
|
||||||
|
maxReconnectionDelay: 10000,
|
||||||
|
minReconnectionDelay: 1000,
|
||||||
|
reconnectionDelayGrowFactor: 1.3,
|
||||||
|
connectionTimeout: 10000,
|
||||||
|
maxRetries: Infinity,
|
||||||
|
debug: true,
|
||||||
|
};
|
||||||
|
const webSocket: WebSocket = new ReconnectingWebSocket.default(
|
||||||
|
url,
|
||||||
|
[],
|
||||||
|
socketOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
webSocket.onopen = () => {
|
||||||
|
console.log("WebSocket connection opened");
|
||||||
|
const socket = toSocket(webSocket);
|
||||||
|
const reader = new WebSocketMessageReader(socket);
|
||||||
|
const writer = new WebSocketMessageWriter(socket);
|
||||||
|
const languageClient = createLanguageClient({ reader, writer });
|
||||||
|
languageClient.start();
|
||||||
|
reader.onClose(() => languageClient.stop());
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onerror = (error) => {
|
||||||
|
console.error("WebSocket error:", error);
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onclose = (event) => {
|
||||||
|
console.log("WebSocket connection closed:", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
webSocket.close();
|
||||||
|
};
|
||||||
|
}, [monacoRef]);
|
||||||
|
|
||||||
const handleEditorMount = async (
|
const handleEditorMount = async (
|
||||||
editor: editor.IStandaloneCodeEditor,
|
editor: editor.IStandaloneCodeEditor,
|
||||||
monaco: Monaco
|
monaco: Monaco
|
||||||
@@ -75,6 +155,8 @@ export function CodeEditor() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
shikiToMonaco(highlighter, monacoRef.current);
|
shikiToMonaco(highlighter, monacoRef.current);
|
||||||
|
|
||||||
|
await initServices({});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { useCodeEditorStore } from "@/store/codeEditorStore";
|
||||||
|
|
||||||
|
export default function MinimapCheckbox() {
|
||||||
|
const t = useTranslations("Page.PlaygroundPage.Components.minimapCheckbox");
|
||||||
|
const { setIsMinimap } = useCodeEditorStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox onCheckedChange={setIsMinimap} />
|
||||||
|
<Label>{t("label")}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
COriginal,
|
||||||
|
CplusplusOriginal,
|
||||||
|
JavaOriginal,
|
||||||
|
PythonOriginal,
|
||||||
|
TypescriptOriginal,
|
||||||
|
} from "devicons-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
@@ -22,7 +29,6 @@ import {
|
|||||||
import * as actions from "@/actions";
|
import * as actions from "@/actions";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { COriginal, JavaOriginal, PythonOriginal } from "devicons-react";
|
|
||||||
import { useCodeEditorStore } from "@/store/codeEditorStore";
|
import { useCodeEditorStore } from "@/store/codeEditorStore";
|
||||||
import { ChevronRight, File, Folder, FolderOpen } from "lucide-react";
|
import { ChevronRight, File, Folder, FolderOpen } from "lucide-react";
|
||||||
|
|
||||||
@@ -137,10 +143,14 @@ function Tree({ item }: { item: FileTree }) {
|
|||||||
|
|
||||||
if (fileExtension === "c") {
|
if (fileExtension === "c") {
|
||||||
fileIcon = <COriginal />;
|
fileIcon = <COriginal />;
|
||||||
|
} else if (fileExtension === "cpp") {
|
||||||
|
fileIcon = <CplusplusOriginal />;
|
||||||
} else if (fileExtension === "java") {
|
} else if (fileExtension === "java") {
|
||||||
fileIcon = <JavaOriginal />;
|
fileIcon = <JavaOriginal />;
|
||||||
} else if (fileExtension === "py") {
|
} else if (fileExtension === "py") {
|
||||||
fileIcon = <PythonOriginal />;
|
fileIcon = <PythonOriginal />;
|
||||||
|
} else if (fileExtension === "ts") {
|
||||||
|
fileIcon = <TypescriptOriginal />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
@@ -148,8 +158,10 @@ function Tree({ item }: { item: FileTree }) {
|
|||||||
|
|
||||||
const langMap: { [key: string]: string } = {
|
const langMap: { [key: string]: string } = {
|
||||||
c: "c",
|
c: "c",
|
||||||
|
cpp: "cpp",
|
||||||
java: "java",
|
java: "java",
|
||||||
py: "python",
|
py: "python",
|
||||||
|
ts: "typescript",
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileClick = async () => {
|
const handleFileClick = async () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ModeSwitcher } from "@/components/mode-switcher";
|
|||||||
import LanguageSwitcher from "@/components/language-switcher";
|
import LanguageSwitcher from "@/components/language-switcher";
|
||||||
import LigaturesCheckbox from "./components/ligatures-checkbox";
|
import LigaturesCheckbox from "./components/ligatures-checkbox";
|
||||||
import { PlaygroundSidebar } from "@/app/playground/components/playground-sidebar";
|
import { PlaygroundSidebar } from "@/app/playground/components/playground-sidebar";
|
||||||
|
import MinimapCheckbox from "./components/minimap-checkbox";
|
||||||
|
|
||||||
export default function PlaygroundLayout({
|
export default function PlaygroundLayout({
|
||||||
children,
|
children,
|
||||||
@@ -26,6 +27,7 @@ export default function PlaygroundLayout({
|
|||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
<ThemeSelector />
|
<ThemeSelector />
|
||||||
<LigaturesCheckbox />
|
<LigaturesCheckbox />
|
||||||
|
<MinimapCheckbox />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto px-3">
|
<div className="ml-auto px-3">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ interface CodeEditorState {
|
|||||||
path: string;
|
path: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
isMinimap: boolean;
|
||||||
isLigature: boolean;
|
isLigature: boolean;
|
||||||
setLang: (lang: string) => void;
|
setLang: (lang: string) => void;
|
||||||
setPath: (path: string) => void;
|
setPath: (path: string) => void;
|
||||||
setTheme: (theme: string) => void;
|
setTheme: (theme: string) => void;
|
||||||
setValue: (value: string) => void;
|
setValue: (value: string) => void;
|
||||||
|
setIsMinimap: (isMinimap: boolean) => void;
|
||||||
setIsLigature: (isLigature: boolean) => void;
|
setIsLigature: (isLigature: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,10 +20,12 @@ export const useCodeEditorStore = create<CodeEditorState>((set) => ({
|
|||||||
path: "",
|
path: "",
|
||||||
theme: "one-dark-pro",
|
theme: "one-dark-pro",
|
||||||
value: "",
|
value: "",
|
||||||
|
isMinimap: false,
|
||||||
isLigature: false,
|
isLigature: false,
|
||||||
setLang: (lang) => set({ lang }),
|
setLang: (lang) => set({ lang }),
|
||||||
setPath: (path) => set({ path }),
|
setPath: (path) => set({ path }),
|
||||||
setTheme: (theme) => set({ theme }),
|
setTheme: (theme) => set({ theme }),
|
||||||
setValue: (value) => set({ value }),
|
setValue: (value) => set({ value }),
|
||||||
|
setIsMinimap: (isMinimap) => set({ isMinimap }),
|
||||||
setIsLigature: (isLigature) => set({ isLigature }),
|
setIsLigature: (isLigature) => set({ isLigature }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user