diff --git a/.cargo/config b/.cargo/config
deleted file mode 100644
index d364ed9..0000000
--- a/.cargo/config
+++ /dev/null
@@ -1,2 +0,0 @@
-[build]
-rustflags = "--cfg=web_sys_unstable_apis"
diff --git a/.gitignore b/.gitignore
index 1e0417a..ff3bf11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -135,7 +135,7 @@ flycheck_*.el
*.webp
# Portable Network Graphics
-*.png
+# *.png
# Animated Portable Network Graphics
*.apng
diff --git a/Makefile.toml b/Makefile.toml
index 4af9c6e..8db8519 100644
--- a/Makefile.toml
+++ b/Makefile.toml
@@ -36,13 +36,18 @@ script = '''
npm run build --workspace=packages/app
'''
+[tasks.build-frontend]
+script = '''
+npm run build --workspace=packages/frontend
+'''
+
[tasks.build-backend]
script = '''
cargo build -p backend --release
'''
[tasks.build]
-dependencies = ["build-server", "build-bindings", "build-app", "build-backend"]
+dependencies = ["build-server", "build-bindings", "build-app","build-frontend", "build-backend"]
[tasks.clean-server]
script = '''
@@ -60,19 +65,32 @@ rm -rf packages/app/dist
rm -rf packages/app/assets/wasm
'''
+[tasks.clean-frontend]
+script = '''
+rm -rf packages/frontend/.next
+'''
+
[tasks.clean]
-dependencies = ["clean-server", "clean-app"]
+dependencies = ["clean-server", "clean-app", "clean-frontend"]
[tasks.format]
script = '''
cargo +nightly fmt --all
'''
-[tasks.run]
+[tasks.run-server]
script = '''
-./target/release/backend --frontend_folder packages/app/dist --port 9000
+./target/release/backend --frontend_folder packages/app/dist --port 4444
'''
+[tasks.run-frontend]
+script = '''
+npm run start --workspace=packages/frontend
+'''
+
+[tasks.run]
+run_task = { name = ["run-server", "run-frontend"], parallel = true }
+
[tasks.test-backend]
script = '''
cargo test
@@ -83,8 +101,13 @@ script = '''
npm run test --workspace=packages/app
'''
+[tasks.test-frontend]
+script = '''
+npm run test --workspace=packages/frontend
+'''
+
[tasks.test]
-dependencies = ["test-backend", "test-app"]
+dependencies = ["test-backend", "test-app", "test-frontend"]
[tasks.docker-build]
script = '''
diff --git a/package.json b/package.json
index 115ea97..a0515fd 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,8 @@
{
"private": true,
"workspaces": [
- "packages/app"
+ "packages/app",
+ "packages/frontend"
],
"scripts": {
"test": "npm run test --workspaces"
diff --git a/.eslintrc.yaml b/packages/app/.eslintrc.yaml
similarity index 100%
rename from .eslintrc.yaml
rename to packages/app/.eslintrc.yaml
diff --git a/packages/app/src/app.ts b/packages/app/src/app.ts
index 4ea74fe..ea22d4f 100644
--- a/packages/app/src/app.ts
+++ b/packages/app/src/app.ts
@@ -166,7 +166,7 @@ export default class App {
(async () => {
const result = await compileRequest(
// FIXME: This should be configurable
- { compileUrl: "http://localhost:9000/compile" },
+ { compileUrl: "http://localhost:4444/compile" },
{ source: code }
);
diff --git a/packages/frontend/.eslintrc.json b/packages/frontend/.eslintrc.json
new file mode 100644
index 0000000..d2dd4da
--- /dev/null
+++ b/packages/frontend/.eslintrc.json
@@ -0,0 +1,16 @@
+{
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/no-inferrable-types": "off",
+ "@typescript-eslint/no-extra-semi": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "prefer-const": "off",
+ "@typescript-eslint/no-namespace": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/ban-types": "off",
+ "@typescript-eslint/ban-ts-comment": "off"
+ },
+
+ "extends": ["next/core-web-vitals", "next/typescript"]
+}
diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore
new file mode 100644
index 0000000..d32cc78
--- /dev/null
+++ b/packages/frontend/.gitignore
@@ -0,0 +1,40 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/packages/frontend/README.md b/packages/frontend/README.md
new file mode 100644
index 0000000..e215bc4
--- /dev/null
+++ b/packages/frontend/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/packages/frontend/components.json b/packages/frontend/components.json
new file mode 100644
index 0000000..d710b49
--- /dev/null
+++ b/packages/frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/packages/frontend/next.config.ts b/packages/frontend/next.config.ts
new file mode 100644
index 0000000..da728e3
--- /dev/null
+++ b/packages/frontend/next.config.ts
@@ -0,0 +1,19 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+ async rewrites() {
+ return [
+ {
+ source: "/compile",
+ destination: "http://localhost:4444/compile",
+ },
+ {
+ source: "/health",
+ destination: "http://localhost:4444/health",
+ },
+ ];
+ },
+};
+
+export default nextConfig;
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
new file mode 100644
index 0000000..8e78246
--- /dev/null
+++ b/packages/frontend/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "frontend",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@monaco-editor/react": "^4.6.0",
+ "@radix-ui/react-accordion": "^1.2.2",
+ "@radix-ui/react-checkbox": "^1.1.2",
+ "@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-radio-group": "^1.2.1",
+ "@radix-ui/react-slot": "^1.1.0",
+ "@statelyai/inspect": "^0.4.0",
+ "@xstate/store": "^2.6.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "debounce": "^2.2.0",
+ "immer": "^10.1.1",
+ "jotai": "^2.10.3",
+ "json-rpc-2.0": "^1.3.0",
+ "lodash": "^4.17.21",
+ "lucide-react": "^0.468.0",
+ "monaco-editor": "^0.52.2",
+ "monaco-editor-core": "^0.52.2",
+ "monaco-languageclient": "^1.0.1",
+ "nanoid": "^5.0.9",
+ "next": "15.0.3",
+ "next-auth": "^5.0.0-beta.25",
+ "next-themes": "^0.4.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.4.0",
+ "tailwind-merge": "^2.5.5",
+ "tailwindcss-animate": "^1.0.7",
+ "vscode-languageserver-protocol": "^3.17.5",
+ "zustand": "^5.0.2"
+ },
+ "devDependencies": {
+ "@types/lodash": "^4.17.13",
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "15.0.3",
+ "postcss": "^8",
+ "sass": "^1.83.0",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5.7.2"
+ }
+}
diff --git a/packages/frontend/postcss.config.mjs b/packages/frontend/postcss.config.mjs
new file mode 100644
index 0000000..1a69fd2
--- /dev/null
+++ b/packages/frontend/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+};
+
+export default config;
diff --git a/packages/frontend/src/app/api/[...nextauth]/route.ts b/packages/frontend/src/app/api/[...nextauth]/route.ts
new file mode 100644
index 0000000..2fcb3cd
--- /dev/null
+++ b/packages/frontend/src/app/api/[...nextauth]/route.ts
@@ -0,0 +1,2 @@
+import { handlers } from "@/lib/auth" // Referring to the auth.ts we just created
+export const { GET, POST } = handlers
\ No newline at end of file
diff --git a/packages/frontend/src/app/fonts/GeistMonoVF.woff b/packages/frontend/src/app/fonts/GeistMonoVF.woff
new file mode 100644
index 0000000..f2ae185
Binary files /dev/null and b/packages/frontend/src/app/fonts/GeistMonoVF.woff differ
diff --git a/packages/frontend/src/app/fonts/GeistVF.woff b/packages/frontend/src/app/fonts/GeistVF.woff
new file mode 100644
index 0000000..1b62daa
Binary files /dev/null and b/packages/frontend/src/app/fonts/GeistVF.woff differ
diff --git a/packages/frontend/src/app/globals.css b/packages/frontend/src/app/globals.css
new file mode 100644
index 0000000..43d989a
--- /dev/null
+++ b/packages/frontend/src/app/globals.css
@@ -0,0 +1,125 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* body {
+ font-family: Arial, Helvetica, sans-serif;
+} */
+
+@layer base {
+ :root,
+ .vs-light {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+
+ --blue: #033c73;
+ --indigo: #6610f2;
+ --purple: #6f42c1;
+ --pink: #e83e8c;
+ --red: #c71c22;
+ --orange: #fd7e14;
+ --yellow: #dd5600;
+ --green: #73a839;
+ --teal: #20c997;
+ --cyan: #2fa4e7;
+ --white: #fff;
+ --gray: #868e96;
+ --gray-dark: #343a40;
+ --primary: #2fa4e7;
+ --secondary: #e9ecef;
+ --success: #73a839;
+ --info: #033c73;
+ --warning: #dd5600;
+ --danger: #c71c22;
+ --light: #f8f9fa;
+ --text: #343a40;
+ --dark: #343a40;
+ --body-bg: #fff;
+ --text-bg-mark: #fcf8e3;
+ --custom-select: #fff;
+ --breakpoint-xs: 0;
+ --breakpoint-sm: 576px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 992px;
+ --breakpoint-xl: 1200px;
+ --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
+ "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ }
+ .vs-dark,
+ .dark {
+ --background: 234 20% 21%;
+ --foreground: 238 17% 69%;
+ --card: 237 23% 17%;
+ --card-foreground: 238 17% 69%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 237 18% 30%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 226 15% 29%;
+ --input: 226 15% 29%;
+ --ring: 226 15% 29%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+@keyframes spinner-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(1turn);
+ }
+}
+
+.animate-spinner-linear-spin {
+ animation: spinner-spin 0.8s linear infinite;
+}
+
+.animate-spinner-ease-spin {
+ animation: spinner-spin 0.8s ease infinite;
+}
diff --git a/packages/frontend/src/app/globals.scss b/packages/frontend/src/app/globals.scss
new file mode 100644
index 0000000..8dff636
--- /dev/null
+++ b/packages/frontend/src/app/globals.scss
@@ -0,0 +1,182 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* body {
+ font-family: Arial, Helvetica, sans-serif;
+} */
+
+@layer base {
+ :root,
+ .vs-light {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+
+ --blue: #033c73;
+ --indigo: #6610f2;
+ --purple: #6f42c1;
+ --pink: #e83e8c;
+ --red: #c71c22;
+ --orange: #fd7e14;
+ --yellow: #dd5600;
+ --green: #73a839;
+ --teal: #20c997;
+ --cyan: #2fa4e7;
+ --white: #fff;
+ --gray: #868e96;
+ --gray-dark: #343a40;
+ --primary: #2fa4e7;
+ --secondary: #e9ecef;
+ --success: #73a839;
+ --info: #033c73;
+ --warning: #dd5600;
+ --danger: #c71c22;
+ --light: #f8f9fa;
+ --text: #343a40;
+ --dark: #343a40;
+ --body-bg: #fff;
+ --text-bg-mark: #fcf8e3;
+ --custom-select: #fff;
+ --breakpoint-xs: 0;
+ --breakpoint-sm: 576px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 992px;
+ --breakpoint-xl: 1200px;
+ --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
+ "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ }
+ .vs-dark,
+ .dark {
+ --background: 234 20% 21%;
+ --foreground: 238 17% 69%;
+ --card: 237 23% 17%;
+ --card-foreground: 238 17% 69%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 237 18% 30%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 226 15% 29%;
+ --input: 226 15% 29%;
+ --ring: 226 15% 29%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+@keyframes spinner-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(1turn);
+ }
+}
+
+.animate-spinner-linear-spin {
+ animation: spinner-spin 0.8s linear infinite;
+}
+
+.animate-spinner-ease-spin {
+ animation: spinner-spin 0.8s ease infinite;
+}
+
+
+.explorer-bg-hover {
+ position: relative;
+
+
+ &::after {
+ content: "";
+ background-color: hsl(var(--foreground) / 0.05);
+ opacity: 0;
+ position: absolute;
+ width: 200%;
+ height: 24px;
+ transition-duration: 150ms;
+ z-index: -1;
+ left: 50%;
+ transform: translateX(-50%);
+
+ }
+
+ &:hover {
+ &::after {
+ opacity: 1;
+ }
+ }
+
+ &.active {
+ &::after {
+ opacity: 0.3;
+ background-color: hsl(var(--primary) / 0.3);
+ }
+ }
+}
+
+
+.explorer-bg-drag {
+ position: relative;
+
+ &::after {
+ content: "";
+ background-color: hsl(var(--foreground) / 0.05);
+ opacity: 0;
+ position: absolute;
+ width: 200%;
+ inset: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ transition-duration: 150ms;
+ z-index: -1;
+ }
+
+ &.active:not(:has(&:hover)) {
+ &::after {
+ opacity: 1;
+ }
+ }
+}
diff --git a/packages/frontend/src/app/layout.tsx b/packages/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..f5b4b80
--- /dev/null
+++ b/packages/frontend/src/app/layout.tsx
@@ -0,0 +1,43 @@
+import type { Metadata } from "next";
+import localFont from "next/font/local";
+import "./globals.scss";
+import EditorProvider from "@/context/EditorProvider";
+import { Provider } from "jotai";
+import ThemeProvider from "@/components/ThemeProvider";
+import { SessionProvider } from "next-auth/react";
+
+const geistSans = localFont({
+ src: "./fonts/GeistVF.woff",
+ variable: "--font-geist-sans",
+ weight: "100 900",
+});
+const geistMono = localFont({
+ src: "./fonts/GeistMonoVF.woff",
+ variable: "--font-geist-mono",
+ weight: "100 900",
+});
+
+export const metadata: Metadata = {
+ title: "Solang",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/packages/frontend/src/app/page.tsx b/packages/frontend/src/app/page.tsx
new file mode 100644
index 0000000..2145928
--- /dev/null
+++ b/packages/frontend/src/app/page.tsx
@@ -0,0 +1,28 @@
+import Console from "@/components/Console";
+import Editor from "@/components/Editor";
+import Header from "@/components/Header";
+import SidePanel from "@/components/SidePanel";
+import Sidebar from "@/components/Sidebar";
+import HomeTab from "@/components/HomeTab";
+
+export default function Home() {
+ return (
+
+ );
+}
diff --git a/packages/frontend/src/app/state.ts b/packages/frontend/src/app/state.ts
new file mode 100644
index 0000000..5571c2d
--- /dev/null
+++ b/packages/frontend/src/app/state.ts
@@ -0,0 +1,46 @@
+import { isDarkTheme } from "@/lib/theme";
+import { generateRandomId } from "@/lib/utils";
+import { atom, useSetAtom } from "jotai";
+import { create } from "zustand";
+import { combine } from "zustand/middleware";
+
+export const ConsoleState = atom<{ id: string; message: string }[]>([]);
+
+export function useAddConsole() {
+ const setState = useSetAtom(ConsoleState);
+ return (message: string) => {
+ const id = generateRandomId();
+ setState((state) => [...state, { id, message }]);
+ };
+}
+
+export const useSettingsStore = create(
+ combine(
+ {
+ monacoTheme: isDarkTheme() ? "vs-dark" : "vs-light",
+ },
+ (set) => ({
+ setMonacoTheme(theme: "vs-dark" | "vs-light") {
+ set((state) => ({ ...state, monacoTheme: theme }));
+ },
+ }),
+ ),
+);
+
+export enum SidebarView {
+ FILE_EXPLORER = "FILE-EXPLORER",
+ SETTINGS = "SETTINGS",
+}
+
+export const useAppStore = create(
+ combine(
+ {
+ sidebar: SidebarView.FILE_EXPLORER,
+ },
+ (set) => ({
+ setSidebar(sidebar: SidebarView) {
+ set((state) => ({ ...state, sidebar }));
+ },
+ }),
+ ),
+);
diff --git a/packages/frontend/src/assets/image/solang-logo.png b/packages/frontend/src/assets/image/solang-logo.png
new file mode 100644
index 0000000..ffdc7ba
Binary files /dev/null and b/packages/frontend/src/assets/image/solang-logo.png differ
diff --git a/packages/frontend/src/components/Accordion.tsx b/packages/frontend/src/components/Accordion.tsx
new file mode 100644
index 0000000..52a8a05
--- /dev/null
+++ b/packages/frontend/src/components/Accordion.tsx
@@ -0,0 +1,46 @@
+import { cn } from "@/lib/utils";
+import { ComponentProps, createContext, useContext, useState } from "react";
+import Hide from "./Hide";
+import { ChevronDown, ChevronRight, ChevronUp } from "lucide-react";
+
+const AccordionContext = createContext({
+ open: false,
+ // setOpen: ((open: boolean) => {}) as React.Dispatch>,
+});
+
+export function AccordionTrigger({ className, children, ...props }: ComponentProps<"div">) {
+ // const { open } = useContext(AccordionContext);
+ return (
+
+ {children}
+
+ );
+}
+
+export function AccordionContent({ className, children, ...props }: ComponentProps<"div">) {
+ const { open } = useContext(AccordionContext);
+
+ if (!open) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function Accordion({ className, open, children, ...props }: ComponentProps<"div"> & { open: boolean }) {
+ // const [open, setOpen] = useState(false);
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/frontend/src/components/Console.tsx b/packages/frontend/src/components/Console.tsx
new file mode 100644
index 0000000..891c670
--- /dev/null
+++ b/packages/frontend/src/components/Console.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { store } from "@/state";
+import { MessageTypeName } from "@/types/log";
+import { useSelector } from "@xstate/store/react";
+import { useEffect, useRef } from "react";
+
+function Console() {
+ const logs = useSelector(store, (state) => state.context.logs);
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ const element = containerRef.current;
+ if (element) {
+ element.scrollTo({
+ top: element.scrollHeight,
+ behavior: "smooth",
+ });
+ }
+ }, [logs]);
+
+ return (
+
+ {logs.map((item) => (
+
+
+ {MessageTypeName[item.type]}: {item.message}
+
+
+ ))}
+
+ );
+}
+
+export default Console;
diff --git a/packages/frontend/src/components/Editor/Editor.tsx b/packages/frontend/src/components/Editor/Editor.tsx
new file mode 100644
index 0000000..785e16b
--- /dev/null
+++ b/packages/frontend/src/components/Editor/Editor.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import MonacoEditor from "@monaco-editor/react";
+import Spinner from "../Spinner";
+import { useTheme } from "next-themes";
+import { init, mountService } from "@/lib/editor";
+import { useFileContent } from "@/state/hooks";
+import { store } from "@/state";
+import { useSelector } from "@xstate/store/react";
+
+function Editor() {
+ const { resolvedTheme } = useTheme();
+ const theme = { dark: "vs-dark", light: "vs-light" }[resolvedTheme!] || resolvedTheme;
+ const code = useFileContent();
+ const { fontSize } = useSelector(store, (state) => state.context.preferences);
+
+ return (
+
+ }
+ onChange={(value) => store.send({ type: "changeContent", content: value || "" })}
+ options={{ fontSize }}
+ />
+
+ );
+}
+
+export default Editor;
diff --git a/packages/frontend/src/components/Editor/index.tsx b/packages/frontend/src/components/Editor/index.tsx
new file mode 100644
index 0000000..f07fcb5
--- /dev/null
+++ b/packages/frontend/src/components/Editor/index.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import dynamic from "next/dynamic";
+
+const Editor = dynamic(() => import("./Editor"), { ssr: false });
+
+export default Editor;
diff --git a/packages/frontend/src/components/FileExplorer/components/File.tsx b/packages/frontend/src/components/FileExplorer/components/File.tsx
new file mode 100644
index 0000000..e3b47c9
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/components/File.tsx
@@ -0,0 +1,61 @@
+import { store } from "@/state";
+import { FileType } from "@/types/explorer";
+import { FileIcon } from "lucide-react";
+import Hide from "@/components/Hide";
+import { useSelector } from "@xstate/store/react";
+import { cn, onEnter } from "@/lib/utils";
+import FileActions from "./FileActions";
+import { useState } from "react";
+import { ParentContext } from "../provider/ParentContext";
+import { useExplorer, useExplorerItem } from "@/state/hooks";
+
+function File({ path, basePath }: FileType & { basePath: string }) {
+ const selected = useSelector(store, (state) => state.context.currentFile);
+ const { name } = useExplorerItem(path);
+ const [editing, setEditing] = useState(name === "");
+ const [newName, setNewName] = useState(name);
+
+ function setCurrentPath(e: React.MouseEvent) {
+ store.send({ type: "setCurrentPath", path });
+ }
+
+ function handleFileEdit() {
+ store.send({ type: "renameFile", path, basePath, name: newName });
+ setEditing(false);
+ }
+
+ return (
+
+
+
+
+
+ {name}}>
+ setNewName(e.target.value)}
+ autoFocus
+ className="w-full rounded flex-1 outline-none ring-1 ring-primary/40 px-1"
+ value={newName}
+ onBlur={handleFileEdit}
+ placeholder="name..."
+ onKeyUp={onEnter(handleFileEdit)}
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default File;
diff --git a/packages/frontend/src/components/FileExplorer/components/FileActions.tsx b/packages/frontend/src/components/FileExplorer/components/FileActions.tsx
new file mode 100644
index 0000000..4ce9b81
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/components/FileActions.tsx
@@ -0,0 +1,20 @@
+import { useParentContext } from "../provider/ParentContext";
+import IconButton from "@/components/IconButton";
+import { PencilIcon, TrashIcon } from "lucide-react";
+import { store } from "@/state";
+
+function FileActions() {
+ const { setEditing, path, basePath } = useParentContext();
+ return (
+
+
setEditing(true)} className="active:opacity-50 duration-150">
+
+
+
store.send({ type: "deleteFile", path, basePath })}>
+
+
+
+ );
+}
+
+export default FileActions;
diff --git a/packages/frontend/src/components/FileExplorer/components/Folder.tsx b/packages/frontend/src/components/FileExplorer/components/Folder.tsx
new file mode 100644
index 0000000..34759ac
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/components/Folder.tsx
@@ -0,0 +1,115 @@
+import { Accordion, AccordionContent, AccordionTrigger } from "@/components/Accordion";
+import { store } from "@/state";
+import { useExplorer } from "@/state/hooks";
+import { FolderIcon, FolderOpen } from "lucide-react";
+import Hide from "@/components/Hide";
+import { cn, onEnter } from "@/lib/utils";
+import FolderActions from "./FolderActions";
+import { useState } from "react";
+import { ParentContext } from "../provider/ParentContext";
+import RenderNode from "./RenderNode";
+import { logger } from "@/state/utils";
+
+function Folder({ path, basePath }: { path: string; basePath: string }) {
+ const { items, open, name } = useExplorer(path);
+ const keys = Object.keys(items);
+ const [editing, setEditing] = useState(name === "");
+ const [newName, setNewName] = useState(name);
+ const [dropping, setDropping] = useState(false);
+
+ function handleToggle() {
+ store.send({ type: "toggleFolder", path });
+ }
+
+ function confirmFolderEdit() {
+ store.send({ type: "renameFolder", path, basePath, name: newName });
+ setEditing(false);
+ }
+
+ async function handleFileDrop(e: React.DragEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const fileArray = Array.from(e.dataTransfer.files);
+ const readFiles = fileArray.map(async (file) => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = (e) => {
+ resolve({ name: file.name, content: e.target?.result });
+ };
+
+ reader.onerror = (e) => {
+ reject(`Error reading file ${file.name}`);
+ };
+
+ // Read as text (adjust if needed for images or binary files)
+ reader.readAsText(file);
+ });
+ });
+
+ const files = (await Promise.all(readFiles).catch(() => {
+ logger.error("Error reading files");
+ return [];
+ })) as any;
+
+ if (files) {
+ store.send({ type: "addFiles", basePath: path, files });
+ }
+
+ setDropping(false);
+ }
+
+ return (
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ setDropping(true);
+ }}
+ onDragLeave={() => setDropping(false)}
+ onDrop={handleFileDrop}
+ >
+
+ }>
+
+
+
+ {name}}>
+ setNewName(e.target.value)}
+ autoFocus
+ className="w-full rounded flex-1 outline-none ring-1 ring-primary/40 px-1"
+ value={newName}
+ onBlur={confirmFolderEdit}
+ placeholder="name..."
+ onKeyUp={onEnter(confirmFolderEdit)}
+ />
+
+
+
+
+ {} }}>
+
+
+
+
+
+
+ {keys.map((key, index) => (
+
+ ))}
+
+
+
+ );
+}
+
+export default Folder;
diff --git a/packages/frontend/src/components/FileExplorer/components/FolderActions.tsx b/packages/frontend/src/components/FileExplorer/components/FolderActions.tsx
new file mode 100644
index 0000000..f8e93e0
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/components/FolderActions.tsx
@@ -0,0 +1,34 @@
+import IconButton from "@/components/IconButton";
+import { useParentContext } from "../provider/ParentContext";
+import { FileIcon, FolderIcon, PencilIcon, TrashIcon } from "lucide-react";
+import { store } from "@/state";
+
+function FolderActions() {
+ const { path, editing, setEditing } = useParentContext();
+
+ function handleAddFile() {
+ store.send({ basePath: path, content: "", name: "", type: "addFile" });
+ }
+
+ return (
+ e.stopPropagation()}
+ >
+
store.send({ type: "addFolder", basePath: path, name: "" })}>
+
+
+
+
+
+
setEditing(true)} className="active:opacity-50 duration-150">
+
+
+
store.send({ type: "deleteFolder", path })}>
+
+
+
+ );
+}
+
+export default FolderActions;
diff --git a/packages/frontend/src/components/FileExplorer/components/RenderNode.tsx b/packages/frontend/src/components/FileExplorer/components/RenderNode.tsx
new file mode 100644
index 0000000..9bcf0c9
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/components/RenderNode.tsx
@@ -0,0 +1,16 @@
+import { ExplorerNode, ExpNodeType, FileType } from "@/types/explorer";
+import Folder from "./Folder";
+import File from "./File";
+
+function isFile(node: ExplorerNode): node is FileType {
+ return node.type === ExpNodeType.FILE;
+}
+
+function RenderNode({ node, basePath }: { node: ExplorerNode; basePath: string }) {
+ if (isFile(node)) {
+ return ;
+ }
+ return ;
+}
+
+export default RenderNode;
diff --git a/packages/frontend/src/components/FileExplorer/index.tsx b/packages/frontend/src/components/FileExplorer/index.tsx
new file mode 100644
index 0000000..0aade6f
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/index.tsx
@@ -0,0 +1,15 @@
+import { FolderType } from "@/types/explorer";
+import RenderNode from "./components/RenderNode";
+
+function FileExplorer({ root }: { root: FolderType }) {
+ return (
+
+ );
+}
+
+export default FileExplorer;
diff --git a/packages/frontend/src/components/FileExplorer/provider/ParentContext.ts b/packages/frontend/src/components/FileExplorer/provider/ParentContext.ts
new file mode 100644
index 0000000..fcd3709
--- /dev/null
+++ b/packages/frontend/src/components/FileExplorer/provider/ParentContext.ts
@@ -0,0 +1,14 @@
+import { createContext, useContext } from "react";
+
+export const ParentContext = createContext({
+ path: "",
+ basePath: "",
+ name: "",
+ editing: false,
+ setEditing: ((editing: boolean) => {}) as React.Dispatch>,
+ confirmEdit: () => {},
+});
+
+export function useParentContext() {
+ return useContext(ParentContext);
+}
diff --git a/packages/frontend/src/components/Footer.tsx b/packages/frontend/src/components/Footer.tsx
new file mode 100644
index 0000000..b003b75
--- /dev/null
+++ b/packages/frontend/src/components/Footer.tsx
@@ -0,0 +1,20 @@
+import { AlertTriangle } from "lucide-react";
+
+function Footer() {
+ return (
+
+
+
+
+ Did you know? To prototype on a uniswap v4 hooks, you can create a Multi Sig Swap Hook workspace. Template
+ created by the cookbook team.
+
+
+
+ );
+}
+
+export default Footer;
\ No newline at end of file
diff --git a/packages/frontend/src/components/Header.tsx b/packages/frontend/src/components/Header.tsx
new file mode 100644
index 0000000..0ef69e2
--- /dev/null
+++ b/packages/frontend/src/components/Header.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+import { cn, downloadBlob } from "@/lib/utils";
+import { FaPlay, FaTimes } from "react-icons/fa";
+import { useAddConsole } from "@/app/state";
+import { useExplorerItem, useFileContent } from "@/state/hooks";
+import { useSelector } from "@xstate/store/react";
+import { store } from "@/state";
+import IconButton from "./IconButton";
+import { useEffect, useRef } from "react";
+import { logger } from "@/state/utils";
+import Hide from "./Hide";
+
+function TabItem({ path }: { path: string }) {
+ const file = useExplorerItem(path);
+ const active = useSelector(store, (state) => state.context.currentFile === path);
+ const itemRef = useRef(null);
+
+ useEffect(() => {
+ if (active && itemRef.current) {
+ itemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }
+ }, [active]);
+
+ return (
+ store.send({ type: "setCurrentPath", path })}
+ className={cn(
+ "bg-foreground/10 px-3 py-1 w-max h-full flex items-center gap-32 border-r duration-150 active:opacity-50",
+ active && "border-t border-t-primary bg-background/20",
+ )}
+ >
+
{file?.name}
+ store.send({ type: "removeTab", path })}
+ >
+
+
+
+ );
+}
+
+function TabHome({ path }: { path: string }) {
+ const active = useSelector(store, (state) => state.context.currentFile === path);
+ const itemRef = useRef(null);
+
+ useEffect(() => {
+ if (active && itemRef.current) {
+ itemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }
+ }, [active]);
+
+ return (
+ store.send({ type: "setCurrentPath", path })}
+ className={cn(
+ "bg-foreground/10 px-3 py-1 w-max h-full flex items-center gap-32 border-r duration-150 active:opacity-50 select-none",
+ active && "border-t border-t-primary bg-background/20",
+ )}
+ >
+
Home
+ store.send({ type: "removeTab", path })}
+ >
+
+
+
+ );
+}
+
+function Header() {
+ const code = useFileContent();
+ const tabs = useSelector(store, (state) => state.context.tabs);
+ const containerRef = useRef(null);
+
+ async function handleCompile() {
+ if (!code) {
+ return logger.error("Error: No Source Code Found");
+ }
+
+ logger.info("Compiling contract...");
+
+ const opts: RequestInit = {
+ method: "POST",
+ mode: "cors",
+ credentials: "same-origin",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ source: code,
+ }),
+ };
+
+ const { result, success, message } = await fetch("/compile", opts).then(async (res) => {
+ console.log(res);
+ const result = await res.json().catch(() => null);
+
+ if (!result) {
+ return {
+ success: false,
+ message: res.statusText,
+ result: null,
+ };
+ }
+
+ return {
+ success: res.ok,
+ message: res.statusText,
+ result: result,
+ };
+ });
+
+ if (success) {
+ if (result.type === "SUCCESS") {
+ const wasm = result.payload.wasm;
+ downloadBlob(wasm);
+ logger.info("Contract compiled successfully!");
+ } else {
+ const message = result.payload.compile_stderr;
+ logger.error(message);
+ }
+ } else {
+ logger.error(message);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ {[...tabs].map((tab) => (
+ }>
+
+
+ ))}
+
+
+
+ );
+}
+
+export default Header;
diff --git a/packages/frontend/src/components/Hide.tsx b/packages/frontend/src/components/Hide.tsx
new file mode 100644
index 0000000..f339582
--- /dev/null
+++ b/packages/frontend/src/components/Hide.tsx
@@ -0,0 +1,19 @@
+import React, { ReactElement, ReactNode } from "react";
+
+interface Props {
+ open: boolean | undefined | null | string;
+ children: ReactNode;
+ fallback?: ReactElement;
+}
+
+function Hide({ open, children, fallback }: Props) {
+ if (open) {
+ return children;
+ } else if (fallback) {
+ return fallback;
+ }
+
+ return null;
+}
+
+export default Hide;
\ No newline at end of file
diff --git a/packages/frontend/src/components/HomeTab.tsx b/packages/frontend/src/components/HomeTab.tsx
new file mode 100644
index 0000000..ace0f63
--- /dev/null
+++ b/packages/frontend/src/components/HomeTab.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { store } from "@/state";
+import { useSelector } from "@xstate/store/react";
+import React from "react";
+import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
+
+function Item({ title, href, className }: { title: string; href: string; className?: string }) {
+ return (
+
+ {title}
+
+ );
+}
+
+function HomeTab() {
+ const current = useSelector(store, (state) => state.context.currentFile);
+
+ if (current !== "home") {
+ return null;
+ }
+
+ return (
+
+
+
+
Solang
+ IDE for Solang Development
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default HomeTab;
diff --git a/packages/frontend/src/components/IconButton.tsx b/packages/frontend/src/components/IconButton.tsx
new file mode 100644
index 0000000..f818160
--- /dev/null
+++ b/packages/frontend/src/components/IconButton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils";
+
+function IconButton({ children, onClick, className, ...props }: React.HTMLAttributes) {
+ function handleClick(e: React.MouseEvent) {
+ e.stopPropagation();
+ onClick?.(e);
+ }
+ return (
+
+ );
+}
+
+export default IconButton;
diff --git a/packages/frontend/src/components/Router.tsx b/packages/frontend/src/components/Router.tsx
new file mode 100644
index 0000000..4418dc5
--- /dev/null
+++ b/packages/frontend/src/components/Router.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { store } from "@/state";
+import { useSelector } from "@xstate/store/react";
+import React from "react";
+import HomeTab from "./HomeTab";
+import Editor from "./Editor";
+import initState from "@/state/inistate";
+
+initState();
+
+function Router() {
+ const current = useSelector(store, (state) => state.context.currentFile);
+
+ if (current === "home") {
+ return ;
+ }
+
+ return ;
+}
+
+export default Router;
diff --git a/packages/frontend/src/components/Settings/components/GoogleBackup.tsx b/packages/frontend/src/components/Settings/components/GoogleBackup.tsx
new file mode 100644
index 0000000..4cc1965
--- /dev/null
+++ b/packages/frontend/src/components/Settings/components/GoogleBackup.tsx
@@ -0,0 +1,58 @@
+import Hide from "@/components/Hide";
+import Spinner from "@/components/Spinner";
+import { Button } from "@/components/ui/button";
+import createGoogleBackup from "@/lib/googlebackup";
+import { store } from "@/state";
+import { logger } from "@/state/utils";
+import { LogOut } from "lucide-react";
+import { signIn, signOut, useSession } from "next-auth/react";
+import { useState } from "react";
+
+function GoogleBackup() {
+ const { status, data: session } = useSession();
+ const [loading, setLoading] = useState(false);
+
+ async function handleCreateBackup() {
+ if (status === "unauthenticated") {
+ return signIn();
+ }
+
+ if (!(session as any).accessToken) {
+ return;
+ }
+
+ setLoading(true);
+ logger.info("Creating Google Drive Backup");
+
+ const state = store.getSnapshot();
+
+ await createGoogleBackup(state.context.explorer, (session as any).accessToken, state.context.files).finally(() => {
+ setLoading(false);
+ });
+
+ logger.info("Google Drive Backup Complete");
+ }
+
+ return (
+
+
+ Google Backup
+
+
+
+
+
+
+
+
+ );
+}
+
+export default GoogleBackup;
diff --git a/packages/frontend/src/components/Settings/components/Preferences.tsx b/packages/frontend/src/components/Settings/components/Preferences.tsx
new file mode 100644
index 0000000..c09a1b2
--- /dev/null
+++ b/packages/frontend/src/components/Settings/components/Preferences.tsx
@@ -0,0 +1,37 @@
+import { Checkbox } from "@/components/ui/checkbox";
+import { Input } from "@/components/ui/input";
+import { store } from "@/state";
+import { useSelector } from "@xstate/store/react";
+
+function Preferences() {
+ const {autoFormat,autoSave,fontSize} = useSelector(store, (state) => state.context.preferences);
+
+ return (
+
+
Preferences
+
+
+
Font Size
+ store.send({ type: "changeFontSize", fontSize: parseInt(e.target.value) })}
+ />
+
+
+
Auto Save
+
+
+
+
Auto Format
+
+
+
+
+ );
+}
+
+export default Preferences;
diff --git a/packages/frontend/src/components/Settings/components/ThemeSwitcher.tsx b/packages/frontend/src/components/Settings/components/ThemeSwitcher.tsx
new file mode 100644
index 0000000..e5b4cb1
--- /dev/null
+++ b/packages/frontend/src/components/Settings/components/ThemeSwitcher.tsx
@@ -0,0 +1,38 @@
+import { Checkbox } from "@/components/ui/checkbox";
+import { useTheme } from "next-themes";
+import { useId } from "react";
+import { Label } from "@/components/ui/label";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+
+function ThemeItem({ value, title }: { value: string; title: string }) {
+ const { setTheme } = useTheme();
+ const id = useId();
+
+ return (
+ setTheme(value)}>
+
+
+
+ );
+}
+
+function ThemeSwitcher() {
+ const { resolvedTheme } = useTheme();
+ return (
+
+
Theme
+
+ {[
+ { value: "light", title: "Light Mode" },
+ { value: "dark", title: "Dark Mode" },
+ ].map((theme) => (
+
+ ))}
+
+
+ );
+}
+
+export default ThemeSwitcher;
diff --git a/packages/frontend/src/components/Settings/index.tsx b/packages/frontend/src/components/Settings/index.tsx
new file mode 100644
index 0000000..69ce7b6
--- /dev/null
+++ b/packages/frontend/src/components/Settings/index.tsx
@@ -0,0 +1,18 @@
+import GoogleBackup from "./components/GoogleBackup";
+import Preferences from "./components/Preferences";
+import ThemeSwitcher from "./components/ThemeSwitcher";
+
+function Settings() {
+ return (
+
+ );
+}
+
+export default Settings;
diff --git a/packages/frontend/src/components/SidePanel.tsx b/packages/frontend/src/components/SidePanel.tsx
new file mode 100644
index 0000000..b565f0a
--- /dev/null
+++ b/packages/frontend/src/components/SidePanel.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import Image from "next/image";
+import SolangLogo from "@/assets/image/solang-logo.png";
+import { Button } from "./ui/button";
+import { FaCog } from "react-icons/fa";
+import { SidebarView, useAppStore } from "@/app/state";
+import { Files, LucideFiles } from "lucide-react";
+
+function SidePanel() {
+ const setSidebar = useAppStore((state) => state.setSidebar);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default SidePanel;
diff --git a/packages/frontend/src/components/Sidebar.tsx b/packages/frontend/src/components/Sidebar.tsx
new file mode 100644
index 0000000..ad7d611
--- /dev/null
+++ b/packages/frontend/src/components/Sidebar.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { SidebarView, useAppStore } from "@/app/state";
+import FileExplorer from "./FileExplorer";
+import Settings from "./Settings";
+import { ExpNodeType } from "@/types/explorer";
+import { store } from "@/state";
+
+function Sidebar() {
+ const { sidebar } = useAppStore();
+ const { explorer } = store.getSnapshot().context;
+
+ if (sidebar === SidebarView.SETTINGS) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+}
+
+function SidebarLayout() {
+ return (
+
+
+
+ );
+}
+
+export default SidebarLayout;
diff --git a/packages/frontend/src/components/Spinner.tsx b/packages/frontend/src/components/Spinner.tsx
new file mode 100644
index 0000000..c87ca63
--- /dev/null
+++ b/packages/frontend/src/components/Spinner.tsx
@@ -0,0 +1,37 @@
+import { cn } from "@/lib/utils";
+import { twMerge } from "tailwind-merge";
+
+const shared = [
+ "absolute",
+ "w-full",
+ "h-full",
+ "rounded-full",
+ "border-t-transparent",
+ "border-l-transparent",
+ "border-r-transparent",
+ "border-b-current",
+].join(" ");
+
+interface Props {
+ className?: string;
+ thickness?: number;
+}
+
+function Spinner({ className, thickness = 2 }: Props) {
+ return (
+
+
+
+
+ );
+}
+
+export default Spinner;
diff --git a/packages/frontend/src/components/ThemeProvider.tsx b/packages/frontend/src/components/ThemeProvider.tsx
new file mode 100644
index 0000000..5d16624
--- /dev/null
+++ b/packages/frontend/src/components/ThemeProvider.tsx
@@ -0,0 +1,13 @@
+"use client"
+
+import * as React from "react"
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+
+function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps) {
+ return {children}
+}
+
+export default ThemeProvider
\ No newline at end of file
diff --git a/packages/frontend/src/components/ui/accordion.tsx b/packages/frontend/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..24c788c
--- /dev/null
+++ b/packages/frontend/src/components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/packages/frontend/src/components/ui/button.tsx b/packages/frontend/src/components/ui/button.tsx
new file mode 100644
index 0000000..36496a2
--- /dev/null
+++ b/packages/frontend/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/packages/frontend/src/components/ui/checkbox.tsx b/packages/frontend/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..b5e91f3
--- /dev/null
+++ b/packages/frontend/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/packages/frontend/src/components/ui/input.tsx b/packages/frontend/src/components/ui/input.tsx
new file mode 100644
index 0000000..68551b9
--- /dev/null
+++ b/packages/frontend/src/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef>(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/packages/frontend/src/components/ui/label.tsx b/packages/frontend/src/components/ui/label.tsx
new file mode 100644
index 0000000..5341821
--- /dev/null
+++ b/packages/frontend/src/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/packages/frontend/src/components/ui/radio-group.tsx b/packages/frontend/src/components/ui/radio-group.tsx
new file mode 100644
index 0000000..e9bde17
--- /dev/null
+++ b/packages/frontend/src/components/ui/radio-group.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/packages/frontend/src/context/EditorProvider.tsx b/packages/frontend/src/context/EditorProvider.tsx
new file mode 100644
index 0000000..a12840a
--- /dev/null
+++ b/packages/frontend/src/context/EditorProvider.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { createContext, MutableRefObject, ReactNode, useRef } from "react";
+import { editor } from "monaco-editor-core";
+
+export const EditorContext = createContext>(null as never);
+
+function EditorProvider({ children }: { children: ReactNode }) {
+ const editorRef = useRef(null);
+ return {children};
+}
+
+export default EditorProvider;
diff --git a/packages/frontend/src/hooks/useEditor.tsx b/packages/frontend/src/hooks/useEditor.tsx
new file mode 100644
index 0000000..1789ff8
--- /dev/null
+++ b/packages/frontend/src/hooks/useEditor.tsx
@@ -0,0 +1,8 @@
+import { EditorContext } from "@/context/EditorProvider";
+import { useContext } from "react";
+
+function useEditor() {
+ return useContext(EditorContext);
+}
+
+export default useEditor;
diff --git a/packages/frontend/src/lib/auth.ts b/packages/frontend/src/lib/auth.ts
new file mode 100644
index 0000000..876cb21
--- /dev/null
+++ b/packages/frontend/src/lib/auth.ts
@@ -0,0 +1,33 @@
+import NextAuth from "next-auth";
+import Google from "next-auth/providers/google";
+
+export const { handlers, signIn, signOut, auth } = NextAuth({
+ callbacks: {
+ async jwt({ token, account }) {
+ if (account) {
+ token.accessToken = account?.access_token;
+ token.refreshToken = account?.refresh_token;
+ }
+ return token;
+ },
+
+ async session({ session, token }) {
+ (session as any).accessToken = (token as any).accessToken;
+ (session as any).refreshToken = (token as any).refreshToken;
+ return session;
+ },
+ },
+
+ providers: [
+ Google({
+ authorization: {
+ params: {
+ prompt: "consent",
+ access_type: "offline",
+ response_type: "code",
+ scope: "openid email profile https://www.googleapis.com/auth/drive",
+ },
+ },
+ }),
+ ],
+});
diff --git a/packages/frontend/src/lib/editor/client.ts b/packages/frontend/src/lib/editor/client.ts
new file mode 100644
index 0000000..f2612c3
--- /dev/null
+++ b/packages/frontend/src/lib/editor/client.ts
@@ -0,0 +1,115 @@
+import * as jsrpc from "json-rpc-2.0";
+import * as proto from "vscode-languageserver-protocol";
+
+import { Codec, FromServer, IntoServer } from "./codec";
+import { store } from "@/state";
+
+// const consoleChannel = document.getElementById("channel-console") as HTMLTextAreaElement;
+
+export default class Client extends jsrpc.JSONRPCServerAndClient {
+ afterInitializedHooks: (() => Promise)[] = [];
+ #fromServer: FromServer;
+ diagnostic: proto.PublishDiagnosticsParams = {
+ uri: "",
+ diagnostics: [],
+ };
+
+ constructor(fromServer: FromServer, intoServer: IntoServer) {
+ super(
+ new jsrpc.JSONRPCServer(),
+ new jsrpc.JSONRPCClient(async (json: jsrpc.JSONRPCRequest) => {
+ const encoded = Codec.encode(json);
+ intoServer.enqueue(encoded);
+ console.log({ json });
+ if (null != json.id) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const response = await fromServer.responses.get(json.id)!;
+ console.log({ response });
+ this.client.receive(response as jsrpc.JSONRPCResponse);
+ }
+ }),
+ );
+ this.#fromServer = fromServer;
+ }
+
+ async start(): Promise {
+ // process "window/logMessage": client <- server
+ this.addMethod(proto.LogMessageNotification.type.method, (params) => {
+ const { type, message } = params as { type: proto.MessageType; message: string };
+ store.send({ type: "addLog", message: message, logType: type });
+ });
+
+ // request "initialize": client <-> server
+ await (this.request(proto.InitializeRequest.type.method, {
+ processId: null,
+ clientInfo: {
+ name: "demo-language-client",
+ },
+ capabilities: {},
+ rootUri: null,
+ } as proto.InitializeParams) as Promise);
+
+ // notify "initialized": client --> server
+ this.notify(proto.InitializedNotification.type.method, {});
+
+ await Promise.all(this.afterInitializedHooks.map((f: () => Promise) => f()));
+ await Promise.all([this.processNotifications(), this.processRequests()]);
+ }
+
+ async processNotifications(): Promise {
+ console.log("processNotifications called");
+ for await (const notification of this.#fromServer.notifications) {
+ console.log("notification: ", notification);
+ if (notification.method == "textDocument/publishDiagnostics") {
+ // delete the old diagnostics
+
+ this.diagnostic.diagnostics = [];
+
+ this.diagnostic = notification.params as proto.PublishDiagnosticsParams;
+
+ console.log("diagnostics: ", this.diagnostic);
+ }
+
+ await this.receiveAndSend(notification);
+ }
+ }
+
+ async processRequests(): Promise {
+ for await (const request of this.#fromServer.requests) {
+ console.log(request);
+ await this.receiveAndSend(request);
+ }
+ }
+
+ printToConsole(type: proto.MessageType, message: string): void {
+ console.log({ type, message });
+ // if (consoleChannel) {
+ // switch (type) {
+ // case proto.MessageType.Error: {
+ // consoleChannel.value += " ERROR: ";
+ // break;
+ // }
+ // case proto.MessageType.Warning: {
+ // consoleChannel.value += " WARNING: ";
+ // break;
+ // }
+ // case proto.MessageType.Info: {
+ // consoleChannel.value += " INFO: ";
+ // break;
+ // }
+ // case proto.MessageType.Log: {
+ // consoleChannel.value += " LOG: ";
+ // break;
+ // }
+ // }
+ // consoleChannel.value += message;
+ // consoleChannel.value += "\n";
+ // } else {
+ // console.error("consoleChannel is not defined");
+ // }
+ }
+
+ pushAfterInitializeHook(...hooks: (() => Promise)[]): void {
+ this.afterInitializedHooks.push(...hooks);
+ }
+}
diff --git a/packages/frontend/src/lib/editor/codec/bytes.ts b/packages/frontend/src/lib/editor/codec/bytes.ts
new file mode 100644
index 0000000..fb4df02
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/bytes.ts
@@ -0,0 +1,28 @@
+import { encoder, decoder } from ".";
+
+export default class Bytes {
+ static encode(input: string): Uint8Array {
+ return encoder.encode(input);
+ }
+
+ static decode(input: Uint8Array): string {
+ return decoder.decode(input);
+ }
+
+ static append(
+ constructor: { new (length: number): T },
+ ...arrays: T[]
+ ) {
+ let totalLength = 0;
+ for (const arr of arrays) {
+ totalLength += arr.length;
+ }
+ const result = new constructor(totalLength);
+ let offset = 0;
+ for (const arr of arrays) {
+ result.set(arr, offset);
+ offset += arr.length;
+ }
+ return result;
+ }
+}
diff --git a/packages/frontend/src/lib/editor/codec/demuxer.ts b/packages/frontend/src/lib/editor/codec/demuxer.ts
new file mode 100644
index 0000000..a8f9267
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/demuxer.ts
@@ -0,0 +1,79 @@
+// @ts-nocheck
+
+import * as vsrpc from "vscode-jsonrpc";
+
+import Bytes from "./bytes";
+import PromiseMap from "./map";
+import Queue from "./queue";
+import Tracer from "../tracer";
+
+export default class StreamDemuxer extends Queue {
+ readonly responses: PromiseMap = new PromiseMap();
+ readonly notifications: Queue = new Queue();
+ readonly requests: Queue = new Queue();
+
+ readonly #start: Promise;
+
+ constructor() {
+ super();
+ this.#start = this.start();
+ }
+
+ private async start(): Promise {
+ console.log("RESPONSES", this.responses);
+ let contentLength: null | number = null;
+ let buffer = new Uint8Array();
+
+ for await (const bytes of this) {
+ console.log("bytes", bytes);
+
+ buffer = Bytes.append(Uint8Array, buffer, bytes);
+
+ // check if the content length is known
+ if (null == contentLength) {
+ // if not, try to match the prefixed headers
+ const match = Bytes.decode(buffer).match(/^Content-Length:\s*(\d+)\s*/);
+ if (null == match) continue;
+
+ // try to parse the content-length from the headers
+ const length = parseInt(match[1]);
+ if (isNaN(length)) throw new Error("invalid content length");
+
+ // slice the headers since we now have the content length
+ buffer = buffer.slice(match[0].length);
+
+ // set the content length
+ contentLength = length;
+ }
+
+ // if the buffer doesn't contain a full message; await another iteration
+ if (buffer.length < contentLength) continue;
+
+ // decode buffer to a string
+ const delimited = Bytes.decode(buffer);
+
+ // reset the buffer
+ buffer = buffer.slice(contentLength);
+ // reset the contentLength
+ contentLength = null;
+
+ const message = JSON.parse(delimited) as vsrpc.Message;
+ console.log("message", message);
+ Tracer.server(message);
+
+ // demux the message stream
+ if (vsrpc.Message.isResponse(message) && null != message.id) {
+ this.responses.set(message.id, message);
+ continue;
+ }
+ if (vsrpc.Message.isNotification(message)) {
+ this.notifications.enqueue(message);
+ continue;
+ }
+ if (vsrpc.Message.isRequest(message)) {
+ this.requests.enqueue(message);
+ continue;
+ }
+ }
+ }
+}
diff --git a/packages/frontend/src/lib/editor/codec/headers.ts b/packages/frontend/src/lib/editor/codec/headers.ts
new file mode 100644
index 0000000..eb30fbc
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/headers.ts
@@ -0,0 +1,9 @@
+export default class Headers {
+ static add(message: string): string {
+ return `Content-Length: ${message.length}\r\n\r\n${message}`;
+ }
+
+ static remove(delimited: string): string {
+ return delimited.replace(/^Content-Length:\s*\d+\s*/, "");
+ }
+}
diff --git a/packages/frontend/src/lib/editor/codec/index.ts b/packages/frontend/src/lib/editor/codec/index.ts
new file mode 100644
index 0000000..038acff
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/index.ts
@@ -0,0 +1,46 @@
+import * as jsrpc from "json-rpc-2.0";
+import * as vsrpc from "vscode-jsonrpc";
+
+import Bytes from "./bytes";
+import StreamDemuxer from "./demuxer";
+import Headers from "./headers";
+import Queue from "./queue";
+import Tracer from "../tracer";
+
+export const encoder = new TextEncoder();
+export const decoder = new TextDecoder();
+
+export class Codec {
+ static encode(json: jsrpc.JSONRPCRequest | jsrpc.JSONRPCResponse): Uint8Array {
+ const message = JSON.stringify(json);
+ const delimited = Headers.add(message);
+ return Bytes.encode(delimited);
+ }
+
+ static decode(data: Uint8Array): T {
+ const delimited = Bytes.decode(data);
+ const message = Headers.remove(delimited);
+ return JSON.parse(message) as T;
+ }
+}
+
+// FIXME: tracing effiency
+export class IntoServer extends Queue implements AsyncGenerator {
+ enqueue(item: Uint8Array): void {
+ Tracer.client(Headers.remove(decoder.decode(item)));
+ super.enqueue(item);
+ }
+}
+
+export interface FromServer extends WritableStream {
+ readonly responses: { get(key: number | string): null | Promise };
+ readonly notifications: AsyncGenerator;
+ readonly requests: AsyncGenerator;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export namespace FromServer {
+ export function create(): FromServer {
+ return new StreamDemuxer();
+ }
+}
diff --git a/packages/frontend/src/lib/editor/codec/map.ts b/packages/frontend/src/lib/editor/codec/map.ts
new file mode 100644
index 0000000..2e36ea8
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/map.ts
@@ -0,0 +1,70 @@
+export default class PromiseMap {
+ #map: Map> = new Map();
+
+ get(key: K & { toString(): string }): null | Promise {
+ let initialized: PromiseMap.Entry;
+ // if the entry doesn't exist, set it
+ if (!this.#map.has(key)) {
+ initialized = this.#set(key);
+ } else {
+ // otherwise return the entry
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ initialized = this.#map.get(key)!;
+ }
+ // if the entry is a pending promise, return it
+ if (initialized.status === "pending") {
+ return initialized.promise;
+ } else {
+ // otherwise return null
+ return null;
+ }
+ }
+
+ #set(key: K, value?: V): PromiseMap.Entry {
+ if (this.#map.has(key)) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return this.#map.get(key)!;
+ }
+ // placeholder resolver for entry
+ let resolve = (item: V) => {
+ void item;
+ };
+ // promise for entry (which assigns the resolver
+ const promise = new Promise((resolver) => {
+ resolve = resolver;
+ });
+ // the initialized entry
+ const initialized: PromiseMap.Entry = { status: "pending", resolve, promise };
+ if (null != value) {
+ initialized.resolve(value);
+ }
+ // set the entry
+ this.#map.set(key, initialized);
+ return initialized;
+ }
+
+ set(key: K & { toString(): string }, value: V): this {
+ const initialized = this.#set(key, value);
+
+ console.log({ initialized });
+ // if the promise is pending ...
+ if (initialized.status === "pending") {
+ // ... set the entry status to resolved to free the promise
+ this.#map.set(key, { status: "resolved" });
+ // ... and resolve the promise with the given value
+ initialized.resolve(value);
+ }
+ return this;
+ }
+
+ get size(): number {
+ return this.#map.size;
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export namespace PromiseMap {
+ export type Entry =
+ | { status: "pending"; resolve: (item: V) => void; promise: Promise }
+ | { status: "resolved" };
+}
diff --git a/packages/frontend/src/lib/editor/codec/queue.ts b/packages/frontend/src/lib/editor/codec/queue.ts
new file mode 100644
index 0000000..c052774
--- /dev/null
+++ b/packages/frontend/src/lib/editor/codec/queue.ts
@@ -0,0 +1,121 @@
+export default class Queue implements WritableStream, AsyncGenerator {
+ readonly #promises: Promise[] = [];
+ readonly #resolvers: ((item: T) => void)[] = [];
+ readonly #observers: ((item: T) => void)[] = [];
+
+ #closed = false;
+ #locked = false;
+ readonly #stream: WritableStream;
+
+ static #__add(promises: Promise[], resolvers: ((item: X) => void)[]): void {
+ const prom = new Promise((resolve) => {
+ (window as any).handle = resolve;
+ resolvers.push(resolve);
+ });
+ (window as any).banana = prom;
+ promises.push(prom as any);
+ }
+
+ static #__enqueue(closed: boolean, promises: Promise[], resolvers: ((item: X) => void)[], item: X): void {
+ if (!closed) {
+ if (!resolvers.length) Queue.#__add(promises, resolvers);
+ const resolve = resolvers.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
+ (window as any).bull = item;
+ resolve(item);
+ }
+ }
+
+ constructor() {
+ const closed = this.#closed;
+ const promises = this.#promises;
+ const resolvers = this.#resolvers;
+ this.#stream = new WritableStream({
+ write(item: T): void {
+ Queue.#__enqueue(closed, promises, resolvers, item);
+ },
+ });
+ }
+
+ #add(): void {
+ return Queue.#__add(this.#promises, this.#resolvers);
+ }
+
+ enqueue(item: T): void {
+ const manog = Queue.#__enqueue(this.#closed, this.#promises, this.#resolvers, item);
+
+ console.log({ item, resol: this.#resolvers, observers: this.#observers, promises: this.#promises });
+ return manog;
+ }
+
+ dequeue(): Promise {
+ if (!this.#promises.length) this.#add();
+ const item = this.#promises.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
+ console.log(item);
+ (window as any).mango = item;
+ return item;
+ }
+
+ isEmpty(): boolean {
+ return !this.#promises.length;
+ }
+
+ isBlocked(): boolean {
+ return !!this.#resolvers.length;
+ }
+
+ get length(): number {
+ return this.#promises.length - this.#resolvers.length;
+ }
+
+ async next(): Promise> {
+ const done = false;
+ console.log("This Next is called ");
+ const value = await this.dequeue();
+ console.log({ value });
+ for (const observer of this.#observers) {
+ observer(value);
+ }
+ return { done, value };
+ }
+
+ return(): Promise> {
+ this.close(); // Ensure the stream is closed on return
+ return Promise.resolve({ done: true, value: undefined! });
+ }
+
+ throw(err: Error): Promise> {
+ return new Promise((_resolve, reject) => {
+ reject(err);
+ });
+ }
+
+ [Symbol.asyncIterator](): AsyncGenerator {
+ return this;
+ }
+
+ [Symbol.asyncDispose](): Promise {
+ // Cleanup resources
+ this.#promises.length = 0; // Clear pending promises
+ this.#resolvers.length = 0; // Clear resolvers
+ this.#observers.length = 0; // Clear observers
+ this.#closed = true; // Mark as closed
+ return Promise.resolve();
+ }
+
+ get locked(): boolean {
+ return this.#stream.locked;
+ }
+
+ abort(reason?: Error): Promise {
+ return this.#stream.abort(reason);
+ }
+
+ close(): Promise {
+ this.#closed = true; // Mark the queue as closed
+ return this.#stream.close();
+ }
+
+ getWriter(): WritableStreamDefaultWriter {
+ return this.#stream.getWriter();
+ }
+}
diff --git a/packages/frontend/src/lib/editor/converter/index.ts b/packages/frontend/src/lib/editor/converter/index.ts
new file mode 100644
index 0000000..df1a353
--- /dev/null
+++ b/packages/frontend/src/lib/editor/converter/index.ts
@@ -0,0 +1,1543 @@
+// @ts-nocheck
+import type * as monaco from "monaco-editor/esm/vs/editor/editor.api";
+import * as ls from "vscode-languageserver-protocol";
+import * as Is from "vscode-languageserver-protocol/lib/common/utils/is";
+import {
+ CodeActionParams,
+ CodeLensParams,
+ DocumentFormattingParams,
+ DocumentOnTypeFormattingParams,
+ DocumentRangeFormattingParams,
+ ReferenceParams,
+ RenameParams,
+ TextDocumentPositionParams,
+ Position,
+ TextDocumentIdentifier,
+ CompletionItem,
+ CompletionList,
+ CompletionParams,
+ CompletionContext,
+ CompletionTriggerKind,
+ InsertTextFormat,
+ Range,
+ Diagnostic,
+ CompletionItemKind,
+ Hover,
+ SignatureHelp,
+ SignatureInformation,
+ ParameterInformation,
+ Definition,
+ DefinitionLink,
+ Location,
+ LocationLink,
+ DocumentHighlight,
+ DocumentHighlightKind,
+ SymbolInformation,
+ DocumentSymbolParams,
+ CodeActionContext,
+ DiagnosticSeverity,
+ Command,
+ CodeLens,
+ FormattingOptions,
+ TextEdit,
+ WorkspaceEdit,
+ DocumentLinkParams,
+ DocumentLink,
+ MarkedString,
+ MarkupContent,
+ ColorInformation,
+ ColorPresentation,
+ FoldingRange,
+ FoldingRangeKind,
+ DiagnosticRelatedInformation,
+ MarkupKind,
+ SymbolKind,
+ DocumentSymbol,
+ CodeAction,
+ SignatureHelpContext,
+ SignatureHelpTriggerKind,
+ SemanticTokens,
+ InsertTextMode,
+ AnnotatedTextEdit,
+ ChangeAnnotation,
+ InlayHint,
+ InlayHintLabelPart,
+} from "./services";
+
+export type RecursivePartial = {
+ [P in keyof T]?: RecursivePartial;
+};
+
+export interface ProtocolDocumentLink extends monaco.languages.ILink {
+ data?: unknown;
+}
+export namespace ProtocolDocumentLink {
+ export function is(item: any): item is ProtocolDocumentLink {
+ return !!item && "data" in item;
+ }
+}
+
+export interface ProtocolCodeLens extends monaco.languages.CodeLens {
+ data?: unknown;
+}
+export namespace ProtocolCodeLens {
+ export function is(item: any): item is ProtocolCodeLens {
+ return !!item && "data" in item;
+ }
+}
+
+export interface ProtocolCompletionItem extends monaco.languages.CompletionItem {
+ data?: unknown;
+ fromEdit?: boolean;
+ documentationFormat?: string;
+ originalItemKind?: CompletionItemKind;
+ deprecated?: boolean;
+ insertTextMode?: InsertTextMode;
+}
+export namespace ProtocolCompletionItem {
+ export function is(item: any): item is ProtocolCompletionItem {
+ return !!item && "data" in item;
+ }
+}
+
+export interface ProtocolCodeAction extends monaco.languages.CodeAction {
+ data?: unknown;
+}
+export namespace ProtocolCodeAction {
+ export function is(item: any): item is ProtocolCodeAction {
+ return !!item && "data" in item;
+ }
+}
+
+export interface ProtocolInlayHint extends monaco.languages.InlayHint {
+ data?: unknown;
+}
+export namespace ProtocolInlayHint {
+ export function is(item: any): item is ProtocolInlayHint {
+ return !!item && "data" in item;
+ }
+}
+
+type RangeReplace = { insert: monaco.IRange; replace: monaco.IRange };
+
+function isRangeReplace(v: Partial | RangeReplace): v is RangeReplace {
+ return (v as RangeReplace).insert !== undefined;
+}
+
+export class MonacoToProtocolConverter {
+ public constructor(protected readonly _monaco: typeof monaco) {}
+
+ asPosition(lineNumber: undefined | null, column: undefined | null): {};
+ asPosition(lineNumber: number, column: undefined | null): Pick;
+ asPosition(lineNumber: undefined | null, column: number): Pick;
+ asPosition(lineNumber: number, column: number): Position;
+ asPosition(lineNumber: number | undefined | null, column: number | undefined | null): Partial;
+ asPosition(lineNumber: number | undefined | null, column: number | undefined | null): Partial {
+ const line = lineNumber === undefined || lineNumber === null ? undefined : lineNumber - 1;
+ const character = column === undefined || column === null ? undefined : column - 1;
+ return {
+ line,
+ character,
+ };
+ }
+
+ asRange(range: null): null;
+ asRange(range: undefined): undefined;
+ asRange(range: monaco.IRange): Range;
+ asRange(range: monaco.IRange | undefined): Range | undefined;
+ asRange(range: monaco.IRange | null): Range | null;
+ asRange(range: monaco.IRange | { insert: monaco.IRange; replace: monaco.IRange }): Range;
+ asRange(range: Partial): RecursivePartial;
+ asRange(range: Partial | undefined): RecursivePartial | undefined;
+ asRange(range: Partial | null): RecursivePartial | null;
+ asRange(range: Partial | undefined | null | RangeReplace): RecursivePartial | undefined | null {
+ if (range === undefined) {
+ return undefined;
+ }
+ if (range === null) {
+ return null;
+ }
+
+ if (isRangeReplace(range)) {
+ return this.asRange(range.insert);
+ } else {
+ const start = this.asPosition(range.startLineNumber, range.startColumn);
+ const end = this.asPosition(range.endLineNumber, range.endColumn);
+ return {
+ start,
+ end,
+ };
+ }
+ }
+
+ asLocation(item: monaco.languages.Location): Location;
+ asLocation(item: undefined | null): undefined;
+ asLocation(item: monaco.languages.Location | undefined | null): Location | undefined;
+ asLocation(item: monaco.languages.Location | undefined | null): Location | undefined {
+ if (!item) {
+ return undefined;
+ }
+ const uri = item.uri.toString();
+ const range = this.asRange(item.range);
+ return {
+ uri,
+ range,
+ };
+ }
+
+ asTextDocumentIdentifier(model: monaco.editor.IReadOnlyModel): TextDocumentIdentifier {
+ return {
+ uri: model.uri.toString(),
+ };
+ }
+
+ asTextDocumentPositionParams(
+ model: monaco.editor.IReadOnlyModel,
+ position: monaco.Position,
+ ): TextDocumentPositionParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ position: this.asPosition(position.lineNumber, position.column),
+ };
+ }
+
+ asCompletionParams(
+ model: monaco.editor.IReadOnlyModel,
+ position: monaco.Position,
+ context: monaco.languages.CompletionContext,
+ ): CompletionParams {
+ return Object.assign(this.asTextDocumentPositionParams(model, position), {
+ context: this.asCompletionContext(context),
+ });
+ }
+
+ asCompletionContext(context: monaco.languages.CompletionContext): CompletionContext {
+ return {
+ triggerKind: this.asCompletionTriggerKind(context.triggerKind),
+ triggerCharacter: context.triggerCharacter,
+ };
+ }
+
+ asSignatureHelpContext(context: monaco.languages.SignatureHelpContext): SignatureHelpContext {
+ return {
+ triggerKind: this.asSignatureHelpTriggerKind(context.triggerKind),
+ triggerCharacter: context.triggerCharacter,
+ isRetrigger: context.isRetrigger,
+ activeSignatureHelp: this.asSignatureHelp(context.activeSignatureHelp),
+ };
+ }
+
+ asSignatureHelp(signatureHelp: monaco.languages.SignatureHelp | undefined): SignatureHelp | undefined {
+ if (signatureHelp === undefined) {
+ return undefined;
+ }
+ return {
+ signatures: signatureHelp.signatures.map((signatureInfo) => this.asSignatureInformation(signatureInfo)),
+ activeParameter: signatureHelp.activeParameter,
+ activeSignature: signatureHelp.activeSignature,
+ };
+ }
+
+ asSignatureInformation(signatureInformation: monaco.languages.SignatureInformation): SignatureInformation {
+ return {
+ documentation: this.asMarkupContent(signatureInformation.documentation),
+ label: signatureInformation.label,
+ parameters: signatureInformation.parameters.map((paramInfo) => this.asParameterInformation(paramInfo)),
+ activeParameter: signatureInformation.activeParameter,
+ };
+ }
+
+ asParameterInformation(parameterInformation: monaco.languages.ParameterInformation): ParameterInformation {
+ return {
+ documentation: this.asMarkupContent(parameterInformation.documentation),
+ label: parameterInformation.label,
+ };
+ }
+
+ asMarkupContent(markupContent: string | monaco.IMarkdownString | undefined): string | MarkupContent | undefined {
+ if (markupContent === undefined) {
+ return undefined;
+ }
+ if (typeof markupContent === "string") {
+ return markupContent;
+ }
+ return {
+ kind: MarkupKind.Markdown,
+ value: markupContent.value,
+ };
+ }
+
+ asSignatureHelpTriggerKind(triggerKind: monaco.languages.SignatureHelpTriggerKind): SignatureHelpTriggerKind {
+ switch (triggerKind) {
+ case this._monaco.languages.SignatureHelpTriggerKind.ContentChange:
+ return SignatureHelpTriggerKind.ContentChange;
+ case this._monaco.languages.SignatureHelpTriggerKind.TriggerCharacter:
+ return SignatureHelpTriggerKind.TriggerCharacter;
+ default:
+ return SignatureHelpTriggerKind.Invoke;
+ }
+ }
+
+ asCompletionTriggerKind(triggerKind: monaco.languages.CompletionTriggerKind): CompletionTriggerKind {
+ switch (triggerKind) {
+ case this._monaco.languages.CompletionTriggerKind.TriggerCharacter:
+ return CompletionTriggerKind.TriggerCharacter;
+ case this._monaco.languages.CompletionTriggerKind.TriggerForIncompleteCompletions:
+ return CompletionTriggerKind.TriggerForIncompleteCompletions;
+ default:
+ return CompletionTriggerKind.Invoked;
+ }
+ }
+
+ asCompletionItem(item: monaco.languages.CompletionItem): CompletionItem {
+ const result: CompletionItem = { label: item.label as string };
+ const protocolItem = ProtocolCompletionItem.is(item) ? item : undefined;
+ if (item.detail) {
+ result.detail = item.detail;
+ }
+ if (item.documentation) {
+ if (typeof item.documentation === "string") {
+ result.documentation = item.documentation;
+ } else {
+ result.documentation = this.asDocumentation(
+ protocolItem?.documentationFormat ?? MarkupKind.Markdown,
+ item.documentation,
+ );
+ }
+ }
+ if (item.filterText) {
+ result.filterText = item.filterText;
+ }
+ this.fillPrimaryInsertText(result, item as ProtocolCompletionItem);
+ if (Is.number(item.kind)) {
+ result.kind = this.asCompletionItemKind(item.kind, protocolItem && protocolItem.originalItemKind);
+ }
+ if (item.sortText) {
+ result.sortText = item.sortText;
+ }
+ if (item.additionalTextEdits) {
+ result.additionalTextEdits = this.asTextEdits(item.additionalTextEdits);
+ }
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (item.commitCharacters) {
+ result.commitCharacters = item.commitCharacters.slice();
+ }
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (item.preselect === true || item.preselect === false) {
+ result.preselect = item.preselect;
+ }
+ if (protocolItem) {
+ if (protocolItem.data !== undefined) {
+ result.data = protocolItem.data;
+ }
+ if (protocolItem.deprecated === true || protocolItem.deprecated === false) {
+ result.deprecated = protocolItem.deprecated;
+ }
+ }
+ if (item.tags) {
+ result.tags = item.tags?.slice();
+ }
+ return result;
+ }
+
+ protected asCompletionItemKind(
+ value: monaco.languages.CompletionItemKind,
+ original: CompletionItemKind | undefined,
+ ): CompletionItemKind {
+ if (original !== undefined) {
+ return original;
+ }
+ switch (value) {
+ case this._monaco.languages.CompletionItemKind.Method:
+ return CompletionItemKind.Method;
+ case this._monaco.languages.CompletionItemKind.Function:
+ return CompletionItemKind.Function;
+ case this._monaco.languages.CompletionItemKind.Constructor:
+ return CompletionItemKind.Constructor;
+ case this._monaco.languages.CompletionItemKind.Field:
+ return CompletionItemKind.Field;
+ case this._monaco.languages.CompletionItemKind.Variable:
+ return CompletionItemKind.Variable;
+ case this._monaco.languages.CompletionItemKind.Class:
+ return CompletionItemKind.Class;
+ case this._monaco.languages.CompletionItemKind.Struct:
+ return CompletionItemKind.Struct;
+ case this._monaco.languages.CompletionItemKind.Interface:
+ return CompletionItemKind.Interface;
+ case this._monaco.languages.CompletionItemKind.Module:
+ return CompletionItemKind.Module;
+ case this._monaco.languages.CompletionItemKind.Property:
+ return CompletionItemKind.Property;
+ case this._monaco.languages.CompletionItemKind.Event:
+ return CompletionItemKind.Event;
+ case this._monaco.languages.CompletionItemKind.Operator:
+ return CompletionItemKind.Operator;
+ case this._monaco.languages.CompletionItemKind.Unit:
+ return CompletionItemKind.Unit;
+ case this._monaco.languages.CompletionItemKind.Value:
+ return CompletionItemKind.Value;
+ case this._monaco.languages.CompletionItemKind.Constant:
+ return CompletionItemKind.Constant;
+ case this._monaco.languages.CompletionItemKind.Enum:
+ return CompletionItemKind.Enum;
+ case this._monaco.languages.CompletionItemKind.EnumMember:
+ return CompletionItemKind.EnumMember;
+ case this._monaco.languages.CompletionItemKind.Keyword:
+ return CompletionItemKind.Keyword;
+ case this._monaco.languages.CompletionItemKind.Text:
+ return CompletionItemKind.Text;
+ case this._monaco.languages.CompletionItemKind.Color:
+ return CompletionItemKind.Color;
+ case this._monaco.languages.CompletionItemKind.File:
+ return CompletionItemKind.File;
+ case this._monaco.languages.CompletionItemKind.Reference:
+ return CompletionItemKind.Reference;
+ case this._monaco.languages.CompletionItemKind.Customcolor:
+ return CompletionItemKind.Color;
+ case this._monaco.languages.CompletionItemKind.Folder:
+ return CompletionItemKind.Folder;
+ case this._monaco.languages.CompletionItemKind.TypeParameter:
+ return CompletionItemKind.TypeParameter;
+ case this._monaco.languages.CompletionItemKind.Snippet:
+ return CompletionItemKind.Snippet;
+ default:
+ return (value + 1) as CompletionItemKind;
+ }
+ }
+
+ protected asDocumentation(format: string, documentation: string | monaco.IMarkdownString): string | MarkupContent {
+ switch (format) {
+ case MarkupKind.PlainText:
+ return { kind: format, value: documentation as string };
+ case MarkupKind.Markdown:
+ return { kind: format, value: (documentation as monaco.IMarkdownString).value };
+ default:
+ return `Unsupported Markup content received. Kind is: ${format}`;
+ }
+ }
+
+ protected fillPrimaryInsertText(target: CompletionItem, source: ProtocolCompletionItem): void {
+ let format: InsertTextFormat = InsertTextFormat.PlainText;
+ let text: string | undefined;
+ let range: Range | undefined;
+ if (
+ source.insertTextRules !== undefined &&
+ (source.insertTextRules & this._monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet) === 0
+ ) {
+ format = InsertTextFormat.Snippet;
+ text = source.insertText;
+ }
+ target.insertTextFormat = format;
+
+ text = source.insertText;
+ if (source.range) {
+ range = this.asRange(source.range);
+ }
+
+ target.insertTextFormat = format;
+ if (source.fromEdit && text && range) {
+ target.textEdit = { newText: text, range: range };
+ } else {
+ target.insertText = text;
+ }
+ target.insertTextMode = source.insertTextMode;
+ }
+
+ asTextEdit(edit: monaco.editor.ISingleEditOperation): TextEdit {
+ const range = this.asRange(edit.range)!;
+ return {
+ range,
+ newText: edit.text || "",
+ };
+ }
+
+ asTextEdits(items: monaco.editor.ISingleEditOperation[]): TextEdit[];
+ asTextEdits(items: undefined | null): undefined;
+ asTextEdits(items: monaco.editor.ISingleEditOperation[] | undefined | null): TextEdit[] | undefined;
+ asTextEdits(items: monaco.editor.ISingleEditOperation[] | undefined | null): TextEdit[] | undefined {
+ if (!items) {
+ return undefined;
+ }
+ return items.map((item) => this.asTextEdit(item));
+ }
+
+ asReferenceParams(
+ model: monaco.editor.IReadOnlyModel,
+ position: monaco.Position,
+ options: { includeDeclaration: boolean },
+ ): ReferenceParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ position: this.asPosition(position.lineNumber, position.column),
+ context: { includeDeclaration: options.includeDeclaration },
+ };
+ }
+
+ asDocumentSymbolParams(model: monaco.editor.IReadOnlyModel): DocumentSymbolParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ };
+ }
+
+ asCodeLensParams(model: monaco.editor.IReadOnlyModel): CodeLensParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ };
+ }
+
+ asDiagnosticSeverity(value: monaco.MarkerSeverity): DiagnosticSeverity | undefined {
+ switch (value) {
+ case this._monaco.MarkerSeverity.Error:
+ return DiagnosticSeverity.Error;
+ case this._monaco.MarkerSeverity.Warning:
+ return DiagnosticSeverity.Warning;
+ case this._monaco.MarkerSeverity.Info:
+ return DiagnosticSeverity.Information;
+ case this._monaco.MarkerSeverity.Hint:
+ return DiagnosticSeverity.Hint;
+ }
+ return undefined;
+ }
+
+ asDiagnostic(marker: monaco.editor.IMarkerData): Diagnostic {
+ const range = this.asRange(
+ new this._monaco.Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
+ );
+ const severity = this.asDiagnosticSeverity(marker.severity);
+ const diag = Diagnostic.create(range, marker.message, severity, marker.code as string, marker.source);
+ return diag;
+ }
+
+ asDiagnostics(markers: monaco.editor.IMarkerData[]): Diagnostic[] {
+ if (markers === void 0 || markers === null) {
+ return markers;
+ }
+ return markers.map((marker) => this.asDiagnostic(marker));
+ }
+
+ asCodeActionContext(context: monaco.languages.CodeActionContext, diagnostics: Diagnostic[]): CodeActionContext {
+ if (context === void 0 || context === null) {
+ return context;
+ }
+ // FIXME: CodeActionTriggerKind is missing
+ return CodeActionContext.create(diagnostics, Is.string(context.only) ? [context.only] : undefined, undefined);
+ }
+
+ asCodeActionParams(
+ model: monaco.editor.IReadOnlyModel,
+ range: monaco.Range,
+ context: monaco.languages.CodeActionContext,
+ diagnostics: Diagnostic[],
+ ): CodeActionParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ range: this.asRange(range),
+ context: this.asCodeActionContext(context, diagnostics),
+ };
+ }
+
+ asCommand(item: monaco.languages.Command | undefined | null): Command | undefined {
+ if (item) {
+ let args = item.arguments || [];
+ return Command.create(item.title, item.id, ...args);
+ }
+ return undefined;
+ }
+
+ asCodeLens(item: monaco.languages.CodeLens): CodeLens {
+ let result = CodeLens.create(this.asRange(item.range));
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (ProtocolCodeLens.is(item)) {
+ if (item.data) {
+ result.data = item.data;
+ }
+ }
+ return result;
+ }
+
+ asFormattingOptions(options: monaco.languages.FormattingOptions): FormattingOptions {
+ return { tabSize: options.tabSize, insertSpaces: options.insertSpaces };
+ }
+
+ asDocumentFormattingParams(
+ model: monaco.editor.IReadOnlyModel,
+ options: monaco.languages.FormattingOptions,
+ ): DocumentFormattingParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ options: this.asFormattingOptions(options),
+ };
+ }
+
+ asDocumentRangeFormattingParams(
+ model: monaco.editor.IReadOnlyModel,
+ range: monaco.Range,
+ options: monaco.languages.FormattingOptions,
+ ): DocumentRangeFormattingParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ range: this.asRange(range),
+ options: this.asFormattingOptions(options),
+ };
+ }
+
+ asDocumentOnTypeFormattingParams(
+ model: monaco.editor.IReadOnlyModel,
+ position: monaco.IPosition,
+ ch: string,
+ options: monaco.languages.FormattingOptions,
+ ): DocumentOnTypeFormattingParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ position: this.asPosition(position.lineNumber, position.column),
+ ch,
+ options: this.asFormattingOptions(options),
+ };
+ }
+
+ asRenameParams(model: monaco.editor.IReadOnlyModel, position: monaco.IPosition, newName: string): RenameParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ position: this.asPosition(position.lineNumber, position.column),
+ newName,
+ };
+ }
+
+ asDocumentLinkParams(model: monaco.editor.IReadOnlyModel): DocumentLinkParams {
+ return {
+ textDocument: this.asTextDocumentIdentifier(model),
+ };
+ }
+
+ asDocumentLink(item: monaco.languages.ILink): DocumentLink {
+ let result = DocumentLink.create(this.asRange(item.range));
+ if (item.url) {
+ result.target = typeof item.url === "string" ? item.url : item.url.toString();
+ }
+ if (ProtocolDocumentLink.is(item) && item.data) {
+ result.data = item.data;
+ }
+ if (item.tooltip) {
+ result.tooltip = item.tooltip;
+ }
+ return result;
+ }
+
+ asCodeAction(item: monaco.languages.CodeAction): CodeAction {
+ const result: CodeAction = { title: item.title };
+ const protocolCodeAction = ProtocolCodeAction.is(item) ? item : undefined;
+ if (Is.number(item.kind)) {
+ result.kind = item.kind;
+ }
+ if (item.diagnostics) {
+ result.diagnostics = this.asDiagnostics(item.diagnostics);
+ }
+ if (item.edit) {
+ throw new Error(`VS Code code actions can only be converted to a protocol code action without an edit.`);
+ }
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (item.isPreferred !== undefined) {
+ result.isPreferred = item.isPreferred;
+ }
+ if (item.disabled) {
+ result.disabled = { reason: item.disabled };
+ }
+ if (protocolCodeAction) {
+ if (protocolCodeAction.data !== undefined) {
+ result.data = protocolCodeAction.data;
+ }
+ }
+ return result;
+ }
+
+ asInlayHintLabelPart(part: monaco.languages.InlayHintLabelPart): InlayHintLabelPart {
+ return {
+ value: part.label,
+ command: this.asCommand(part.command),
+ location: this.asLocation(part.location),
+ tooltip: this.asMarkupContent(part.tooltip),
+ };
+ }
+
+ asInlayHintLabel(label: string | monaco.languages.InlayHintLabelPart[]): string | InlayHintLabelPart[] {
+ if (Array.isArray(label)) {
+ return label.map((part) => this.asInlayHintLabelPart(part));
+ }
+ return label;
+ }
+
+ asInlayHint(item: monaco.languages.InlayHint): InlayHint {
+ let result = InlayHint.create(
+ this.asPosition(item.position.lineNumber, item.position.column),
+ this.asInlayHintLabel(item.label),
+ item.kind,
+ );
+ if (ProtocolInlayHint.is(item)) {
+ if (item.data) {
+ result.data = item.data;
+ }
+ }
+ return result;
+ }
+}
+
+export class ProtocolToMonacoConverter {
+ public constructor(protected readonly _monaco: typeof monaco) {}
+
+ asResourceEdits(
+ resource: monaco.Uri,
+ edits: (TextEdit | AnnotatedTextEdit)[],
+ asMetadata: (
+ annotation: ls.ChangeAnnotationIdentifier | undefined,
+ ) => monaco.languages.WorkspaceEditMetadata | undefined,
+ modelVersionId?: number,
+ ): monaco.languages.IWorkspaceTextEdit[] {
+ return edits.map(
+ (edit) =>
+ ({
+ resource: resource,
+ edit: this.asTextEdit(edit),
+ modelVersionId,
+ metadata: AnnotatedTextEdit.is(edit) ? asMetadata(edit.annotationId) : undefined,
+ } as any),
+ );
+ }
+
+ asWorkspaceEditMetadata(changeAnnotation: ChangeAnnotation): monaco.languages.WorkspaceEditMetadata {
+ return {
+ needsConfirmation: changeAnnotation.needsConfirmation === true,
+ label: changeAnnotation.label,
+ description: changeAnnotation.description,
+ };
+ }
+
+ asWorkspaceEdit(item: WorkspaceEdit): monaco.languages.WorkspaceEdit;
+ asWorkspaceEdit(item: undefined | null): undefined;
+ asWorkspaceEdit(item: WorkspaceEdit | undefined | null): monaco.languages.WorkspaceEdit | undefined;
+ asWorkspaceEdit(item: WorkspaceEdit | undefined | null): monaco.languages.WorkspaceEdit | undefined {
+ if (!item) {
+ return undefined;
+ }
+ const sharedMetadata: Map = new Map();
+ if (item.changeAnnotations !== undefined) {
+ for (const key of Object.keys(item.changeAnnotations)) {
+ const metaData = this.asWorkspaceEditMetadata(item.changeAnnotations[key]);
+ sharedMetadata.set(key, metaData);
+ }
+ }
+ const asMetadata = (
+ annotation: ls.ChangeAnnotationIdentifier | undefined,
+ ): monaco.languages.WorkspaceEditMetadata | undefined => {
+ if (annotation === undefined) {
+ return undefined;
+ } else {
+ return sharedMetadata.get(annotation);
+ }
+ };
+ const edits: (monaco.languages.IWorkspaceTextEdit | monaco.languages.IWorkspaceFileEdit)[] = [];
+ if (item.documentChanges) {
+ item.documentChanges.forEach((change) => {
+ if (ls.CreateFile.is(change)) {
+ edits.push({
+ newUri: this._monaco.Uri.parse(change.uri),
+ options: change.options,
+ metadata: asMetadata(change.annotationId),
+ });
+ } else if (ls.RenameFile.is(change)) {
+ edits.push({
+ oldUri: this._monaco.Uri.parse(change.oldUri),
+ newUri: this._monaco.Uri.parse(change.newUri),
+ options: change.options,
+ metadata: asMetadata(change.annotationId),
+ });
+ } else if (ls.DeleteFile.is(change)) {
+ edits.push({
+ oldUri: this._monaco.Uri.parse(change.uri),
+ options: change.options,
+ metadata: asMetadata(change.annotationId),
+ });
+ } else if (ls.TextDocumentEdit.is(change)) {
+ const resource = this._monaco.Uri.parse(change.textDocument.uri);
+ const version = typeof change.textDocument.version === "number" ? change.textDocument.version : undefined;
+ edits.push(...this.asResourceEdits(resource, change.edits, asMetadata, version));
+ } else {
+ console.error(`Unknown workspace edit change received:\n${JSON.stringify(change, undefined, 4)}`);
+ }
+ });
+ } else if (item.changes) {
+ for (const key of Object.keys(item.changes)) {
+ const resource = this._monaco.Uri.parse(key);
+ edits.push(...this.asResourceEdits(resource, item.changes[key], asMetadata));
+ }
+ }
+ return {
+ edits,
+ };
+ }
+
+ asTextEdit(edit: TextEdit): monaco.languages.TextEdit;
+ asTextEdit(edit: undefined | null): undefined;
+ asTextEdit(edit: TextEdit | undefined | null): undefined;
+ asTextEdit(edit: TextEdit | undefined | null): monaco.languages.TextEdit | undefined {
+ if (!edit) {
+ return undefined;
+ }
+ const range = this.asRange(edit.range)!;
+ return {
+ range,
+ text: edit.newText,
+ };
+ }
+
+ asTextEdits(items: TextEdit[]): monaco.languages.TextEdit[];
+ asTextEdits(items: undefined | null): undefined;
+ asTextEdits(items: TextEdit[] | undefined | null): monaco.languages.TextEdit[] | undefined;
+ asTextEdits(items: TextEdit[] | undefined | null): monaco.languages.TextEdit[] | undefined {
+ if (!items) {
+ return undefined;
+ }
+ return items.map((item) => this.asTextEdit(item));
+ }
+
+ asCodeLens(item: CodeLens): monaco.languages.CodeLens;
+ asCodeLens(item: undefined | null): undefined;
+ asCodeLens(item: CodeLens | undefined | null): monaco.languages.CodeLens | undefined;
+ asCodeLens(item: CodeLens | undefined | null): monaco.languages.CodeLens | undefined {
+ if (!item) {
+ return undefined;
+ }
+ const range = this.asRange(item.range);
+ let result = { range };
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (item.data !== void 0 && item.data !== null) {
+ result.data = item.data;
+ }
+ return result;
+ }
+
+ asCodeLensList(items: CodeLens[]): monaco.languages.CodeLensList;
+ asCodeLensList(items: undefined | null): undefined;
+ asCodeLensList(items: CodeLens[] | undefined | null): monaco.languages.CodeLensList | undefined;
+ asCodeLensList(items: CodeLens[] | undefined | null): monaco.languages.CodeLensList | undefined {
+ if (!items) {
+ return undefined;
+ }
+ return {
+ lenses: items.map((codeLens) => this.asCodeLens(codeLens)),
+ dispose: () => {},
+ };
+ }
+
+ asCodeActionList(actions: (Command | CodeAction)[]): monaco.languages.CodeActionList {
+ return {
+ actions: actions.map((action) => this.asCodeAction(action)),
+ dispose: () => {},
+ };
+ }
+
+ asCodeAction(item: Command | CodeAction): ProtocolCodeAction {
+ if (Command.is(item)) {
+ return {
+ command: {
+ id: item.command,
+ title: item.title,
+ arguments: item.arguments,
+ },
+ title: item.title,
+ };
+ }
+ return {
+ title: item.title,
+ command: this.asCommand(item.command),
+ edit: this.asWorkspaceEdit(item.edit),
+ diagnostics: this.asDiagnostics(item.diagnostics),
+ kind: item.kind,
+ disabled: item.disabled ? item.disabled.reason : undefined,
+ isPreferred: item.isPreferred,
+ data: item.data,
+ };
+ }
+
+ asCommand(command: Command): monaco.languages.Command;
+ asCommand(command: undefined): undefined;
+ asCommand(command: Command | undefined): monaco.languages.Command | undefined;
+ asCommand(command: Command | undefined): monaco.languages.Command | undefined {
+ if (!command) {
+ return undefined;
+ }
+ return {
+ id: command.command,
+ title: command.title,
+ arguments: command.arguments,
+ };
+ }
+
+ asDocumentSymbol(value: DocumentSymbol): monaco.languages.DocumentSymbol {
+ const children = value.children && value.children.map((c) => this.asDocumentSymbol(c));
+ return {
+ name: value.name,
+ detail: value.detail || "",
+ kind: this.asSymbolKind(value.kind),
+ tags: value.tags || [],
+ range: this.asRange(value.range),
+ selectionRange: this.asRange(value.selectionRange),
+ children,
+ };
+ }
+
+ asDocumentSymbols(values: SymbolInformation[] | DocumentSymbol[]): monaco.languages.DocumentSymbol[] {
+ if (DocumentSymbol.is(values[0])) {
+ return (values as DocumentSymbol[]).map((s) => this.asDocumentSymbol(s));
+ }
+ return this.asSymbolInformations(values as SymbolInformation[]);
+ }
+
+ asSymbolInformations(values: SymbolInformation[], uri?: monaco.Uri): monaco.languages.DocumentSymbol[];
+ asSymbolInformations(values: undefined | null, uri?: monaco.Uri): undefined;
+ asSymbolInformations(
+ values: SymbolInformation[] | undefined | null,
+ uri?: monaco.Uri,
+ ): monaco.languages.DocumentSymbol[] | undefined;
+ asSymbolInformations(
+ values: SymbolInformation[] | undefined | null,
+ uri?: monaco.Uri,
+ ): monaco.languages.DocumentSymbol[] | undefined {
+ if (!values) {
+ return undefined;
+ }
+ return values.map((information) => this.asSymbolInformation(information, uri));
+ }
+
+ asSymbolInformation(item: SymbolInformation, uri?: monaco.Uri): monaco.languages.DocumentSymbol {
+ const location = this.asLocation(uri ? { ...item.location, uri: uri.toString() } : item.location);
+ return {
+ name: item.name,
+ detail: "",
+ containerName: item.containerName,
+ kind: this.asSymbolKind(item.kind),
+ tags: item.tags || [],
+ range: location.range,
+ selectionRange: location.range,
+ };
+ }
+
+ asSymbolKind(item: SymbolKind): monaco.languages.SymbolKind {
+ if (item <= SymbolKind.TypeParameter) {
+ // Symbol kind is one based in the protocol and zero based in code.
+ return item - 1;
+ }
+ return this._monaco.languages.SymbolKind.Property;
+ }
+
+ asDocumentHighlights(values: DocumentHighlight[]): monaco.languages.DocumentHighlight[];
+ asDocumentHighlights(values: undefined | null): undefined;
+ asDocumentHighlights(
+ values: DocumentHighlight[] | undefined | null,
+ ): monaco.languages.DocumentHighlight[] | undefined;
+ asDocumentHighlights(
+ values: DocumentHighlight[] | undefined | null,
+ ): monaco.languages.DocumentHighlight[] | undefined {
+ if (!values) {
+ return undefined;
+ }
+ return values.map((item) => this.asDocumentHighlight(item));
+ }
+
+ asDocumentHighlight(item: DocumentHighlight): monaco.languages.DocumentHighlight {
+ const range = this.asRange(item.range)!;
+ const kind = Is.number(item.kind) ? this.asDocumentHighlightKind(item.kind) : undefined!;
+ return { range, kind };
+ }
+
+ asDocumentHighlightKind(item: number): monaco.languages.DocumentHighlightKind {
+ switch (item) {
+ case DocumentHighlightKind.Text:
+ return this._monaco.languages.DocumentHighlightKind.Text;
+ case DocumentHighlightKind.Read:
+ return this._monaco.languages.DocumentHighlightKind.Read;
+ case DocumentHighlightKind.Write:
+ return this._monaco.languages.DocumentHighlightKind.Write;
+ }
+ return this._monaco.languages.DocumentHighlightKind.Text;
+ }
+
+ asReferences(values: Location[]): monaco.languages.Location[];
+ asReferences(values: undefined | null): monaco.languages.Location[] | undefined;
+ asReferences(values: Location[] | undefined | null): monaco.languages.Location[] | undefined;
+ asReferences(values: Location[] | undefined | null): monaco.languages.Location[] | undefined {
+ if (!values) {
+ return undefined;
+ }
+ return values.map((location) => this.asLocation(location));
+ }
+
+ asDefinitionResult(item: Definition): monaco.languages.Definition;
+ asDefinitionResult(item: DefinitionLink[]): monaco.languages.Definition;
+ asDefinitionResult(item: undefined | null): undefined;
+ asDefinitionResult(item: Definition | DefinitionLink[] | undefined | null): monaco.languages.Definition | undefined;
+ asDefinitionResult(item: Definition | DefinitionLink[] | undefined | null): monaco.languages.Definition | undefined {
+ if (!item) {
+ return undefined;
+ }
+ if (Is.array(item)) {
+ if (item.length == 0) {
+ return undefined;
+ } else if (LocationLink.is(item[0])) {
+ let links: LocationLink[] = item as LocationLink[];
+ return links.map((location) => this.asLocationLink(location));
+ } else {
+ let locations: Location[] = item as Location[];
+ return locations.map((location) => this.asLocation(location));
+ }
+ } else {
+ return this.asLocation(item);
+ }
+ }
+
+ asLocation(item: Location): monaco.languages.Location;
+ asLocation(item: undefined | null): undefined;
+ asLocation(item: Location | undefined | null): monaco.languages.Location | undefined;
+ asLocation(item: Location | undefined | null): monaco.languages.Location | undefined {
+ if (!item) {
+ return undefined;
+ }
+ const uri = this._monaco.Uri.parse(item.uri);
+ const range = this.asRange(item.range)!;
+ return {
+ uri,
+ range,
+ };
+ }
+
+ asLocationLink(item: undefined | null): undefined;
+ asLocationLink(item: ls.LocationLink): monaco.languages.LocationLink;
+ asLocationLink(item: ls.LocationLink | undefined | null): monaco.languages.LocationLink | undefined {
+ if (!item) {
+ return undefined;
+ }
+ let result: monaco.languages.LocationLink = {
+ uri: this._monaco.Uri.parse(item.targetUri),
+ range: this.asRange(item.targetSelectionRange)!, // See issue: https://github.com/Microsoft/vscode/issues/58649
+ originSelectionRange: this.asRange(item.originSelectionRange),
+ targetSelectionRange: this.asRange(item.targetSelectionRange),
+ };
+ if (!result.targetSelectionRange) {
+ throw new Error(`targetSelectionRange must not be undefined or null`);
+ }
+ return result;
+ }
+
+ asSignatureHelpResult(item: undefined | null): undefined;
+ asSignatureHelpResult(item: SignatureHelp): monaco.languages.SignatureHelpResult;
+ asSignatureHelpResult(item: SignatureHelp | undefined | null): monaco.languages.SignatureHelpResult | undefined;
+ asSignatureHelpResult(item: SignatureHelp | undefined | null): monaco.languages.SignatureHelpResult | undefined {
+ if (!item) {
+ return undefined;
+ }
+ let result = {};
+ if (Is.number(item.activeSignature)) {
+ result.activeSignature = item.activeSignature;
+ } else {
+ // activeSignature was optional in the past
+ result.activeSignature = 0;
+ }
+ if (Is.number(item.activeParameter)) {
+ result.activeParameter = item.activeParameter;
+ } else {
+ // activeParameter was optional in the past
+ result.activeParameter = 0;
+ }
+ if (item.signatures) {
+ result.signatures = this.asSignatureInformations(item.signatures);
+ } else {
+ result.signatures = [];
+ }
+ return {
+ value: result,
+ dispose: () => {},
+ };
+ }
+
+ asSignatureInformations(items: SignatureInformation[]): monaco.languages.SignatureInformation[] {
+ return items.map((item) => this.asSignatureInformation(item));
+ }
+
+ asSignatureInformation(item: SignatureInformation): monaco.languages.SignatureInformation {
+ let result = { label: item.label };
+ if (item.documentation) {
+ result.documentation = this.asDocumentation(item.documentation);
+ }
+ if (item.parameters) {
+ result.parameters = this.asParameterInformations(item.parameters);
+ } else {
+ result.parameters = [];
+ }
+ if (item.activeParameter) {
+ result.activeParameter = item.activeParameter;
+ }
+ return result;
+ }
+
+ asParameterInformations(item: ParameterInformation[]): monaco.languages.ParameterInformation[] {
+ return item.map((item) => this.asParameterInformation(item));
+ }
+
+ asParameterInformation(item: ParameterInformation): monaco.languages.ParameterInformation {
+ let result = { label: item.label };
+ if (item.documentation) {
+ result.documentation = this.asDocumentation(item.documentation);
+ }
+ return result;
+ }
+
+ asHover(hover: Hover): monaco.languages.Hover;
+ asHover(hover: undefined | null): undefined;
+ asHover(hover: Hover | undefined | null): monaco.languages.Hover | undefined;
+ asHover(hover: Hover | undefined | null): monaco.languages.Hover | undefined {
+ if (!hover) {
+ return undefined;
+ }
+ return {
+ contents: this.asHoverContent(hover.contents),
+ range: this.asRange(hover.range),
+ };
+ }
+
+ asHoverContent(contents: MarkedString | MarkedString[] | MarkupContent): monaco.IMarkdownString[] {
+ if (Array.isArray(contents)) {
+ return contents.map((content) => this.asMarkdownString(content));
+ }
+ return [this.asMarkdownString(contents)];
+ }
+
+ asDocumentation(value: string | MarkupContent): string | monaco.IMarkdownString {
+ if (Is.string(value)) {
+ return value;
+ }
+ if (value.kind === MarkupKind.PlainText) {
+ return value.value;
+ }
+ return this.asMarkdownString(value);
+ }
+
+ asMarkdownString(content: MarkedString | MarkupContent): monaco.IMarkdownString {
+ if (MarkupContent.is(content)) {
+ return {
+ value: content.value,
+ };
+ }
+ if (Is.string(content)) {
+ return { value: content };
+ }
+ const { language, value } = content;
+ return {
+ value: "```" + language + "\n" + value + "\n```",
+ };
+ }
+
+ asSeverity(severity?: ls.DiagnosticSeverity): monaco.MarkerSeverity {
+ if (severity === 1) {
+ return this._monaco.MarkerSeverity.Error;
+ }
+ if (severity === 2) {
+ return this._monaco.MarkerSeverity.Warning;
+ }
+ if (severity === 3) {
+ return this._monaco.MarkerSeverity.Info;
+ }
+ return this._monaco.MarkerSeverity.Hint;
+ }
+
+ asDiagnostics(diagnostics: undefined): undefined;
+ asDiagnostics(diagnostics: Diagnostic[]): monaco.editor.IMarkerData[];
+ asDiagnostics(diagnostics: Diagnostic[] | undefined): monaco.editor.IMarkerData[] | undefined;
+ asDiagnostics(diagnostics: Diagnostic[] | undefined): monaco.editor.IMarkerData[] | undefined {
+ if (!diagnostics) {
+ return undefined;
+ }
+ return diagnostics.map((diagnostic) => this.asDiagnostic(diagnostic));
+ }
+
+ asDiagnostic(diagnostic: Diagnostic): monaco.editor.IMarkerData {
+ return {
+ code: typeof diagnostic.code === "number" ? diagnostic.code.toString() : diagnostic.code,
+ severity: this.asSeverity(diagnostic.severity),
+ message: diagnostic.message,
+ source: diagnostic.source,
+ startLineNumber: diagnostic.range.start.line + 1,
+ startColumn: diagnostic.range.start.character + 1,
+ endLineNumber: diagnostic.range.end.line + 1,
+ endColumn: diagnostic.range.end.character + 1,
+ relatedInformation: this.asRelatedInformations(diagnostic.relatedInformation),
+ tags: diagnostic.tags,
+ };
+ }
+
+ asRelatedInformations(
+ relatedInformation?: DiagnosticRelatedInformation[],
+ ): monaco.editor.IRelatedInformation[] | undefined {
+ if (!relatedInformation) {
+ return undefined;
+ }
+ return relatedInformation.map((item) => this.asRelatedInformation(item));
+ }
+
+ asRelatedInformation(relatedInformation: DiagnosticRelatedInformation): monaco.editor.IRelatedInformation {
+ return {
+ resource: this._monaco.Uri.parse(relatedInformation.location.uri),
+ startLineNumber: relatedInformation.location.range.start.line + 1,
+ startColumn: relatedInformation.location.range.start.character + 1,
+ endLineNumber: relatedInformation.location.range.end.line + 1,
+ endColumn: relatedInformation.location.range.end.character + 1,
+ message: relatedInformation.message,
+ };
+ }
+
+ asCompletionResult(
+ result: CompletionItem[] | CompletionList | null | undefined,
+ defaultRange: monaco.IRange,
+ ): monaco.languages.CompletionList {
+ if (!result) {
+ return {
+ incomplete: false,
+ suggestions: [],
+ };
+ }
+ if (Array.isArray(result)) {
+ const suggestions = result.map((item) => this.asCompletionItem(item, defaultRange));
+ return {
+ incomplete: false,
+ suggestions,
+ };
+ }
+ return {
+ incomplete: result.isIncomplete,
+ suggestions: result.items.map((item) => this.asCompletionItem(item, defaultRange)),
+ };
+ }
+
+ asCompletionItem(item: CompletionItem, defaultRange: monaco.IRange | RangeReplace): ProtocolCompletionItem {
+ const result = { label: item.label };
+ if (item.detail) {
+ result.detail = item.detail;
+ }
+ if (item.documentation) {
+ result.documentation = this.asDocumentation(item.documentation);
+ result.documentationFormat = Is.string(item.documentation) ? undefined : item.documentation.kind;
+ }
+ if (item.filterText) {
+ result.filterText = item.filterText;
+ }
+ const insertText = this.asCompletionInsertText(item, defaultRange);
+ result.insertText = insertText.insertText;
+ result.range = insertText.range;
+ result.fromEdit = insertText.fromEdit;
+ if (insertText.isSnippet) {
+ result.insertTextRules = this._monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
+ }
+ if (Is.number(item.kind)) {
+ let [itemKind, original] = this.asCompletionItemKind(item.kind);
+ result.kind = itemKind;
+ if (original) {
+ result.originalItemKind = original;
+ }
+ }
+ if (item.sortText) {
+ result.sortText = item.sortText;
+ }
+ if (item.additionalTextEdits) {
+ result.additionalTextEdits = this.asTextEdits(item.additionalTextEdits);
+ }
+ if (Is.stringArray(item.commitCharacters)) {
+ result.commitCharacters = item.commitCharacters.slice();
+ }
+ if (item.command) {
+ result.command = this.asCommand(item.command);
+ }
+ if (item.deprecated === true || item.deprecated === false) {
+ result.deprecated = item.deprecated;
+ }
+ if (item.preselect === true || item.preselect === false) {
+ result.preselect = item.preselect;
+ }
+ if (item.data !== undefined) {
+ result.data = item.data;
+ }
+ if (item.deprecated === true || item.deprecated === false) {
+ result.deprecated = item.deprecated;
+ }
+ if (item.insertTextMode) {
+ result.insertTextMode = item.insertTextMode;
+ }
+ if (item.tags) {
+ result.tags = item.tags;
+ }
+ return result;
+ }
+
+ asCompletionItemKind(
+ value: CompletionItemKind,
+ ): [monaco.languages.CompletionItemKind, CompletionItemKind | undefined] {
+ if (CompletionItemKind.Text <= value && value <= CompletionItemKind.TypeParameter) {
+ switch (value) {
+ case CompletionItemKind.Text:
+ return [this._monaco.languages.CompletionItemKind.Text, undefined];
+ case CompletionItemKind.Method:
+ return [this._monaco.languages.CompletionItemKind.Method, undefined];
+ case CompletionItemKind.Function:
+ return [this._monaco.languages.CompletionItemKind.Function, undefined];
+ case CompletionItemKind.Constructor:
+ return [this._monaco.languages.CompletionItemKind.Constructor, undefined];
+ case CompletionItemKind.Field:
+ return [this._monaco.languages.CompletionItemKind.Field, undefined];
+ case CompletionItemKind.Variable:
+ return [this._monaco.languages.CompletionItemKind.Variable, undefined];
+ case CompletionItemKind.Class:
+ return [this._monaco.languages.CompletionItemKind.Class, undefined];
+ case CompletionItemKind.Interface:
+ return [this._monaco.languages.CompletionItemKind.Interface, undefined];
+ case CompletionItemKind.Module:
+ return [this._monaco.languages.CompletionItemKind.Module, undefined];
+ case CompletionItemKind.Property:
+ return [this._monaco.languages.CompletionItemKind.Property, undefined];
+ case CompletionItemKind.Unit:
+ return [this._monaco.languages.CompletionItemKind.Unit, undefined];
+ case CompletionItemKind.Value:
+ return [this._monaco.languages.CompletionItemKind.Value, undefined];
+ case CompletionItemKind.Enum:
+ return [this._monaco.languages.CompletionItemKind.Enum, undefined];
+ case CompletionItemKind.Keyword:
+ return [this._monaco.languages.CompletionItemKind.Keyword, undefined];
+ case CompletionItemKind.Snippet:
+ return [this._monaco.languages.CompletionItemKind.Snippet, undefined];
+ case CompletionItemKind.Color:
+ return [this._monaco.languages.CompletionItemKind.Color, undefined];
+ case CompletionItemKind.File:
+ return [this._monaco.languages.CompletionItemKind.File, undefined];
+ case CompletionItemKind.Reference:
+ return [this._monaco.languages.CompletionItemKind.Reference, undefined];
+ case CompletionItemKind.Folder:
+ return [this._monaco.languages.CompletionItemKind.Folder, undefined];
+ case CompletionItemKind.EnumMember:
+ return [this._monaco.languages.CompletionItemKind.EnumMember, undefined];
+ case CompletionItemKind.Constant:
+ return [this._monaco.languages.CompletionItemKind.Constant, undefined];
+ case CompletionItemKind.Struct:
+ return [this._monaco.languages.CompletionItemKind.Struct, undefined];
+ case CompletionItemKind.Event:
+ return [this._monaco.languages.CompletionItemKind.Event, undefined];
+ case CompletionItemKind.Operator:
+ return [this._monaco.languages.CompletionItemKind.Operator, undefined];
+ case CompletionItemKind.TypeParameter:
+ return [this._monaco.languages.CompletionItemKind.TypeParameter, undefined];
+ default:
+ return [value - 1, undefined];
+ }
+ }
+ return [CompletionItemKind.Text, value];
+ }
+
+ asCompletionInsertText(
+ item: CompletionItem,
+ defaultRange: monaco.IRange | RangeReplace,
+ ): { insertText: string; range: monaco.IRange | RangeReplace; fromEdit: boolean; isSnippet: boolean } {
+ const isSnippet = item.insertTextFormat === InsertTextFormat.Snippet;
+ if (item.textEdit) {
+ if (TextEdit.is(item.textEdit)) {
+ const range = this.asRange(item.textEdit.range);
+ const value = item.textEdit.newText;
+ return { isSnippet, insertText: value, range, fromEdit: true };
+ } else {
+ const range = {
+ insert: this.asRange(item.textEdit.insert),
+ replace: this.asRange(item.textEdit.replace),
+ };
+ const value = item.textEdit.newText;
+ return { isSnippet, insertText: value, range, fromEdit: true };
+ }
+ }
+ if (item.insertText) {
+ return { isSnippet, insertText: item.insertText, fromEdit: false, range: defaultRange };
+ }
+ return { insertText: item.label, range: defaultRange, fromEdit: false, isSnippet: false };
+ }
+
+ asDocumentLinks(documentLinks: DocumentLink[]): monaco.languages.ILinksList {
+ const links = documentLinks.map((link) => this.asDocumentLink(link));
+ return { links };
+ }
+
+ asDocumentLink(documentLink: DocumentLink): ProtocolDocumentLink {
+ return {
+ range: this.asRange(documentLink.range),
+ url: documentLink.target,
+ data: documentLink.data,
+ tooltip: documentLink.tooltip,
+ };
+ }
+
+ asRange(range: null): null;
+ asRange(range: undefined): undefined;
+ asRange(range: Range): monaco.Range;
+ asRange(range: Range | undefined): monaco.Range | undefined;
+ asRange(range: Range | null): monaco.Range | null;
+ asRange(range: RecursivePartial): Partial;
+ asRange(range: RecursivePartial | undefined): monaco.Range | Partial | undefined;
+ asRange(range: RecursivePartial | null): monaco.Range | Partial | null;
+ asRange(range: RecursivePartial | undefined | null): monaco.Range | Partial | undefined | null {
+ if (range === undefined) {
+ return undefined;
+ }
+ if (range === null) {
+ return null;
+ }
+ const start = this.asPosition(range.start);
+ const end = this.asPosition(range.end);
+ if (start instanceof this._monaco.Position && end instanceof this._monaco.Position) {
+ return new this._monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column);
+ }
+ const startLineNumber = !start || start.lineNumber === undefined ? undefined : start.lineNumber;
+ const startColumn = !start || start.column === undefined ? undefined : start.column;
+ const endLineNumber = !end || end.lineNumber === undefined ? undefined : end.lineNumber;
+ const endColumn = !end || end.column === undefined ? undefined : end.column;
+ return { startLineNumber, startColumn, endLineNumber, endColumn };
+ }
+
+ asPosition(position: null): null;
+ asPosition(position: undefined): undefined;
+ asPosition(position: Position): monaco.Position;
+ asPosition(position: Position | undefined): monaco.Position | undefined;
+ asPosition(position: Position | null): monaco.Position | null;
+ asPosition(position: Partial): Partial;
+ asPosition(position: Partial | undefined): monaco.Position | Partial | undefined;
+ asPosition(position: Partial | null): monaco.Position | Partial | null;
+ asPosition(
+ position: Partial | undefined | null,
+ ): monaco.Position | Partial | undefined | null {
+ if (position === undefined) {
+ return undefined;
+ }
+ if (position === null) {
+ return null;
+ }
+ const { line, character } = position;
+ const lineNumber = line === undefined ? undefined : line + 1;
+ const column = character === undefined ? undefined : character + 1;
+ if (lineNumber !== undefined && column !== undefined) {
+ return new this._monaco.Position(lineNumber, column);
+ }
+ return { lineNumber, column };
+ }
+
+ asColorInformations(items: ColorInformation[]): monaco.languages.IColorInformation[] {
+ return items.map((item) => this.asColorInformation(item));
+ }
+
+ asColorInformation(item: ColorInformation): monaco.languages.IColorInformation {
+ return {
+ range: this.asRange(item.range),
+ color: item.color,
+ };
+ }
+
+ asColorPresentations(items: ColorPresentation[]): monaco.languages.IColorPresentation[] {
+ return items.map((item) => this.asColorPresentation(item));
+ }
+
+ asColorPresentation(item: ColorPresentation): monaco.languages.IColorPresentation {
+ return {
+ label: item.label,
+ textEdit: this.asTextEdit(item.textEdit),
+ additionalTextEdits: this.asTextEdits(item.additionalTextEdits),
+ };
+ }
+
+ asFoldingRanges(items: undefined | null): undefined | null;
+ asFoldingRanges(items: FoldingRange[]): monaco.languages.FoldingRange[];
+ asFoldingRanges(items: FoldingRange[] | undefined | null): monaco.languages.FoldingRange[] | undefined | null {
+ if (!items) {
+ return items;
+ }
+ return items.map((item) => this.asFoldingRange(item));
+ }
+
+ asFoldingRange(item: FoldingRange): monaco.languages.FoldingRange {
+ return {
+ start: item.startLine + 1,
+ end: item.endLine + 1,
+ kind: this.asFoldingRangeKind(item.kind),
+ };
+ }
+
+ asFoldingRangeKind(kind?: string): monaco.languages.FoldingRangeKind | undefined {
+ if (kind) {
+ switch (kind) {
+ case FoldingRangeKind.Comment:
+ return this._monaco.languages.FoldingRangeKind.Comment;
+ case FoldingRangeKind.Imports:
+ return this._monaco.languages.FoldingRangeKind.Imports;
+ case FoldingRangeKind.Region:
+ return this._monaco.languages.FoldingRangeKind.Region;
+ }
+ }
+ return undefined;
+ }
+
+ asSemanticTokens(semanticTokens: SemanticTokens): monaco.languages.SemanticTokens {
+ return {
+ resultId: semanticTokens.resultId,
+ data: Uint32Array.from(semanticTokens.data),
+ };
+ }
+
+ asInlayHintLabelPart(part: InlayHintLabelPart): monaco.languages.InlayHintLabelPart {
+ return {
+ label: part.value,
+ command: this.asCommand(part.command),
+ location: this.asLocation(part.location),
+ tooltip: part.tooltip && this.asMarkdownString(part.tooltip),
+ };
+ }
+
+ asInlayHintLabel(label: string | InlayHintLabelPart[]): string | monaco.languages.InlayHintLabelPart[] {
+ if (Array.isArray(label)) {
+ return label.map((part) => this.asInlayHintLabelPart(part));
+ }
+ return label;
+ }
+
+ asInlayHint(inlayHint: InlayHint): ProtocolInlayHint {
+ return {
+ data: inlayHint.data,
+ label: this.asInlayHintLabel(inlayHint.label),
+ position: this.asPosition(inlayHint.position),
+ kind: inlayHint.kind,
+ paddingLeft: inlayHint.paddingLeft,
+ paddingRight: inlayHint.paddingRight,
+ tooltip: inlayHint.tooltip && this.asMarkdownString(inlayHint.tooltip),
+ };
+ }
+
+ asInlayHintList(items: InlayHint[]): monaco.languages.InlayHintList;
+ asInlayHintList(items: undefined | null): undefined;
+ asInlayHintList(items: InlayHint[] | undefined | null): monaco.languages.InlayHintList | undefined;
+ asInlayHintList(items: InlayHint[] | undefined | null): monaco.languages.InlayHintList | undefined {
+ if (!items) {
+ return undefined;
+ }
+ return {
+ hints: items.map((hint) => this.asInlayHint(hint)),
+ dispose: () => {},
+ };
+ }
+}
diff --git a/packages/frontend/src/lib/editor/converter/services.ts b/packages/frontend/src/lib/editor/converter/services.ts
new file mode 100644
index 0000000..363a8a0
--- /dev/null
+++ b/packages/frontend/src/lib/editor/converter/services.ts
@@ -0,0 +1,417 @@
+//@ts-nocheck
+
+import {
+ DocumentSelector,
+ MessageActionItem,
+ MessageType,
+ TextDocumentPositionParams,
+ ReferenceParams,
+ CodeActionParams,
+ CodeLensParams,
+ DocumentFormattingParams,
+ DocumentRangeFormattingParams,
+ DocumentOnTypeFormattingParams,
+ RenameParams,
+ DocumentLinkParams,
+ WorkspaceClientCapabilities,
+ Diagnostic,
+ CompletionItem,
+ CompletionList,
+ Hover,
+ SignatureHelp,
+ Definition,
+ Location,
+ DocumentHighlight,
+ SymbolInformation,
+ Command,
+ CodeLens,
+ TextEdit,
+ WorkspaceEdit,
+ DocumentLink,
+ TextDocumentSaveReason,
+ DocumentSymbolParams,
+ WorkspaceSymbolParams,
+ TextDocumentContentChangeEvent,
+ CompletionParams,
+ ColorInformation,
+ ColorPresentation,
+ DocumentColorParams,
+ ColorPresentationParams,
+ FoldingRange,
+ FoldingRangeParams,
+ DocumentSymbol,
+ CodeAction,
+ Declaration,
+ SelectionRangeParams,
+ SelectionRange,
+ SemanticTokensParams,
+ SemanticTokens,
+ SemanticTokensEdit,
+ SemanticTokensLegend,
+ SemanticTokensRangeParams,
+ SemanticTokensDeltaParams,
+ InlayHint,
+ InlayHintParams,
+} from "vscode-languageserver-protocol";
+
+import { TextDocument } from "vscode-languageserver-textdocument";
+
+import { Disposable, CancellationToken, Event, Emitter } from "vscode-jsonrpc";
+
+import { URI as Uri } from "vscode-uri";
+// import { TextDocumentShowOptions } from 'vscode';
+
+export { Disposable, CancellationToken, Event, Emitter };
+export * from "vscode-languageserver-protocol/lib/common/api";
+
+export { TextDocument };
+
+export interface Services {
+ languages: Languages;
+ workspace: Workspace;
+ commands?: Commands;
+ window?: Window;
+ env?: Env;
+}
+export namespace Services {
+ const global = window as any;
+ const symbol = Symbol("Services");
+ export type Provider = () => Services;
+ export const get: Provider = () => {
+ const services = global[symbol];
+ if (!services) {
+ throw new Error("Language Client services has not been installed");
+ }
+ return services;
+ };
+ export function install(services: Services): Disposable {
+ if (global[symbol]) {
+ console.warn("Language Client services have been overridden");
+ }
+ global[symbol] = services;
+
+ return Disposable.create(() => (global[symbol] = undefined));
+ }
+}
+
+export interface DiagnosticCollection extends Disposable {
+ set(uri: string, diagnostics: Diagnostic[]): void;
+ get(uri: string): Diagnostic[];
+}
+
+export type ProviderResult = T | undefined | null | PromiseLike;
+
+export interface CompletionItemProvider {
+ provideCompletionItems(
+ params: CompletionParams,
+ token: CancellationToken,
+ ): ProviderResult;
+ resolveCompletionItem?(item: CompletionItem, token: CancellationToken): ProviderResult;
+}
+
+export interface HoverProvider {
+ provideHover(params: TextDocumentPositionParams, token: CancellationToken): ProviderResult;
+}
+
+export enum SignatureHelpTriggerKind {
+ Invoke = 1,
+ TriggerCharacter = 2,
+ ContentChange = 3,
+}
+
+// runtime support
+export enum VsCodeDiagnosticSeverity {
+ Error = 0,
+ Warning = 1,
+ Information = 2,
+ Hint = 3,
+}
+
+export enum ProgressLocation {
+ SourceControl = 1,
+ Window = 10,
+ Notification = 15,
+}
+
+export interface SignatureHelpContext {
+ readonly triggerKind: SignatureHelpTriggerKind;
+ readonly triggerCharacter?: string;
+ readonly isRetrigger: boolean;
+ readonly activeSignatureHelp?: SignatureHelp;
+}
+
+export interface SignatureHelpProvider {
+ readonly triggerCharacters?: ReadonlyArray;
+ readonly retriggerCharacters?: ReadonlyArray;
+ provideSignatureHelp(
+ params: TextDocumentPositionParams,
+ token: CancellationToken,
+ context: SignatureHelpContext,
+ ): ProviderResult;
+}
+
+export interface DefinitionProvider {
+ provideDefinition(params: TextDocumentPositionParams, token: CancellationToken): ProviderResult;
+}
+
+export interface ReferenceProvider {
+ provideReferences(params: ReferenceParams, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentHighlightProvider {
+ provideDocumentHighlights(
+ params: TextDocumentPositionParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface DocumentSymbolProvider {
+ provideDocumentSymbols(
+ params: DocumentSymbolParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface WorkspaceSymbolProvider {
+ provideWorkspaceSymbols(params: WorkspaceSymbolParams, token: CancellationToken): ProviderResult;
+}
+
+export interface CodeActionProvider {
+ provideCodeActions(params: CodeActionParams, token: CancellationToken): ProviderResult<(Command | CodeAction)[]>;
+ resolveCodeAction?(codeAction: T, token: CancellationToken): ProviderResult;
+}
+
+export interface CodeLensProvider {
+ provideCodeLenses(params: CodeLensParams, token: CancellationToken): ProviderResult;
+ resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentFormattingEditProvider {
+ provideDocumentFormattingEdits(
+ params: DocumentFormattingParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface DocumentRangeFormattingEditProvider {
+ provideDocumentRangeFormattingEdits(
+ params: DocumentRangeFormattingParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface OnTypeFormattingEditProvider {
+ provideOnTypeFormattingEdits(
+ params: DocumentOnTypeFormattingParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface RenameProvider {
+ provideRenameEdits(params: RenameParams, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentLinkProvider {
+ provideDocumentLinks(params: DocumentLinkParams, token: CancellationToken): ProviderResult;
+ resolveDocumentLink?(link: DocumentLink, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentIdentifier {
+ uri: string;
+ languageId: string;
+}
+export namespace DocumentIdentifier {
+ export function is(arg: any): arg is DocumentIdentifier {
+ return !!arg && "uri" in arg && "languageId" in arg;
+ }
+}
+
+export interface ImplementationProvider {
+ provideImplementation(params: TextDocumentPositionParams, token: CancellationToken): ProviderResult;
+}
+
+export interface TypeDefinitionProvider {
+ provideTypeDefinition(params: TextDocumentPositionParams, token: CancellationToken): ProviderResult;
+}
+
+export interface DeclarationProvider {
+ provideDeclaration(params: TextDocumentPositionParams, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentColorProvider {
+ provideDocumentColors(params: DocumentColorParams, token: CancellationToken): ProviderResult;
+ provideColorPresentations(
+ params: ColorPresentationParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface FoldingRangeProvider {
+ provideFoldingRanges(params: FoldingRangeParams, token: CancellationToken): ProviderResult;
+}
+
+export interface SelectionRangeProvider {
+ provideSelectionRanges(params: SelectionRangeParams, token: CancellationToken): ProviderResult;
+}
+
+export interface DocumentSemanticTokensProvider {
+ onDidChange?: Event;
+ provideDocumentSemanticTokens(params: SemanticTokensParams, token: CancellationToken): ProviderResult;
+ provideDocumentSemanticTokensEdits?(
+ params: SemanticTokensDeltaParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface DocumentRangeSemanticTokensProvider {
+ provideDocumentRangeSemanticTokens(
+ params: SemanticTokensRangeParams,
+ token: CancellationToken,
+ ): ProviderResult;
+}
+
+export interface InlayHintsProvider {
+ onDidChangeInlayHints?: Event;
+ provideInlayHints(params: InlayHintParams, token: CancellationToken): ProviderResult;
+ resolveInlayHint?(hint: T, token: CancellationToken): ProviderResult;
+}
+
+export interface Languages {
+ match(selector: DocumentSelector, document: DocumentIdentifier): boolean;
+ createDiagnosticCollection?(name?: string): DiagnosticCollection;
+ registerCompletionItemProvider?(
+ selector: DocumentSelector,
+ provider: CompletionItemProvider,
+ ...triggerCharacters: string[]
+ ): Disposable;
+ registerHoverProvider?(selector: DocumentSelector, provider: HoverProvider): Disposable;
+ registerSignatureHelpProvider?(selector: DocumentSelector, provider: SignatureHelpProvider): Disposable;
+ registerDefinitionProvider?(selector: DocumentSelector, provider: DefinitionProvider): Disposable;
+ registerReferenceProvider?(selector: DocumentSelector, provider: ReferenceProvider): Disposable;
+ registerDocumentHighlightProvider?(selector: DocumentSelector, provider: DocumentHighlightProvider): Disposable;
+ registerDocumentSymbolProvider?(selector: DocumentSelector, provider: DocumentSymbolProvider): Disposable;
+ registerWorkspaceSymbolProvider?(provider: WorkspaceSymbolProvider): Disposable;
+ registerCodeActionsProvider?(selector: DocumentSelector, provider: CodeActionProvider): Disposable;
+ registerCodeLensProvider?(selector: DocumentSelector, provider: CodeLensProvider): Disposable;
+ registerDocumentFormattingEditProvider?(
+ selector: DocumentSelector,
+ provider: DocumentFormattingEditProvider,
+ ): Disposable;
+ registerDocumentRangeFormattingEditProvider?(
+ selector: DocumentSelector,
+ provider: DocumentRangeFormattingEditProvider,
+ ): Disposable;
+ registerOnTypeFormattingEditProvider?(
+ selector: DocumentSelector,
+ provider: OnTypeFormattingEditProvider,
+ firstTriggerCharacter: string,
+ ...moreTriggerCharacter: string[]
+ ): Disposable;
+ registerRenameProvider?(selector: DocumentSelector, provider: RenameProvider): Disposable;
+ registerDocumentLinkProvider?(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable;
+ registerImplementationProvider?(selector: DocumentSelector, provider: ImplementationProvider): Disposable;
+ registerTypeDefinitionProvider?(selector: DocumentSelector, provider: TypeDefinitionProvider): Disposable;
+ registerDeclarationProvider?(selector: DocumentSelector, provider: DeclarationProvider): Disposable;
+ registerColorProvider?(selector: DocumentSelector, provider: DocumentColorProvider): Disposable;
+ registerFoldingRangeProvider?(selector: DocumentSelector, provider: FoldingRangeProvider): Disposable;
+ registerSelectionRangeProvider?(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable;
+ registerDocumentSemanticTokensProvider?(
+ selector: DocumentSelector,
+ provider: DocumentSemanticTokensProvider,
+ legend: SemanticTokensLegend,
+ ): Disposable;
+ registerDocumentRangeSemanticTokensProvider?(
+ selector: DocumentSelector,
+ provider: DocumentRangeSemanticTokensProvider,
+ legend: SemanticTokensLegend,
+ ): Disposable;
+ registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable;
+}
+
+export interface TextDocumentDidChangeEvent {
+ readonly textDocument: TextDocument;
+ readonly contentChanges: TextDocumentContentChangeEvent[];
+ readonly isUndoing: boolean;
+ readonly isRedoing: boolean;
+}
+
+export interface TextDocumentWillSaveEvent {
+ readonly textDocument: TextDocument;
+ readonly reason: TextDocumentSaveReason;
+ waitUntil?(PromiseLike: PromiseLike): void;
+}
+
+export enum ConfigurationTarget {
+ Global = 1,
+ Workspace = 2,
+ WorkspaceFolder = 3,
+}
+
+export interface WorkspaceConfiguration {
+ toJSON(): any;
+ get(section: string): T | undefined;
+ get(section: string, defaultValue: T): T;
+ has(section: string): boolean;
+ readonly [key: string]: any;
+}
+
+export interface FileSystemWatcher extends Disposable {
+ readonly onDidCreate: Event;
+ readonly onDidChange: Event;
+ readonly onDidDelete: Event;
+}
+
+export interface ConfigurationChangeEvent {
+ affectsConfiguration(section: string): boolean;
+}
+export interface Configurations {
+ getConfiguration(section?: string, resource?: string): WorkspaceConfiguration;
+ readonly onDidChangeConfiguration: Event;
+}
+
+export interface Workspace {
+ readonly capabilities?: WorkspaceClientCapabilities;
+ readonly rootPath?: string | null;
+ readonly rootUri: string | null;
+ readonly workspaceFolders?: any;
+ readonly onDidChangeWorkspaceFolders?: any;
+ readonly textDocuments: TextDocument[];
+ readonly onDidOpenTextDocument: Event;
+ readonly onDidCloseTextDocument: Event;
+ readonly onDidChangeTextDocument: Event;
+ readonly configurations?: Configurations;
+ readonly onWillSaveTextDocument?: Event;
+ readonly onDidSaveTextDocument?: Event;
+ applyEdit(changes: WorkspaceEdit): PromiseLike;
+ createFileSystemWatcher?(
+ globPattern: string,
+ ignoreCreateEvents?: boolean,
+ ignoreChangeEvents?: boolean,
+ ignoreDeleteEvents?: boolean,
+ ): FileSystemWatcher;
+}
+
+export interface Commands {
+ registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable;
+}
+
+export interface OutputChannel extends Disposable {
+ append(value: string): void;
+ appendLine(line: string): void;
+ show(preserveFocus?: boolean): void;
+}
+
+export interface Window {
+ showMessage(
+ type: MessageType,
+ message: string,
+ ...actions: T[]
+ ): PromiseLike;
+ createOutputChannel?(name: string): OutputChannel;
+ withProgress?: any;
+ showTextDocument?(document: Uri, options?: any): PromiseLike;
+}
+
+export interface Env {
+ openExternal?(document: Uri): PromiseLike;
+}
diff --git a/packages/frontend/src/lib/editor/index.ts b/packages/frontend/src/lib/editor/index.ts
new file mode 100644
index 0000000..917c7d7
--- /dev/null
+++ b/packages/frontend/src/lib/editor/index.ts
@@ -0,0 +1,48 @@
+import monaco from "monaco-editor";
+import { loader, Monaco } from "@monaco-editor/react";
+import Server from "./server";
+import { FromServer, IntoServer } from "./codec";
+import Client from "./client";
+import Language from "./language";
+import { EditorService } from "./services";
+import debounce from "debounce";
+import { protocolToMonaco } from "./utils";
+import { store } from "@/state";
+import { defaultCode } from "@/state/initstate";
+import initState from "@/state/inistate";
+
+const intoServer = new IntoServer();
+const fromServer = FromServer.create();
+const client = new Client(fromServer, intoServer);
+const editorService = new EditorService(client);
+
+let language: Language;
+
+export async function init(monaco: Monaco) {
+ store.send({ type: "setMonaco", monaco });
+ initState();
+ const server = await Server.initialize(intoServer, fromServer);
+ language = Language.initialize(client, monaco);
+
+ return await Promise.all([server.start(), client.start()]);
+}
+
+export async function mountService(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) {
+ const model = editor.getModel()!;
+
+ client.pushAfterInitializeHook(async () => {
+ editorService.fileOpened(model);
+ });
+
+ editor.onDidChangeModelContent(
+ debounce(() => {
+ editorService.fileChanged(model);
+
+ setTimeout(() => {
+ const diagnostic = client.diagnostic;
+ const markers = protocolToMonaco.asDiagnostics(diagnostic.diagnostics);
+ monaco.editor.setModelMarkers(model, "solidity", markers);
+ }, 500);
+ }, 200),
+ );
+}
diff --git a/packages/frontend/src/lib/editor/language/contributes.json b/packages/frontend/src/lib/editor/language/contributes.json
new file mode 100644
index 0000000..69162cb
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/contributes.json
@@ -0,0 +1,68 @@
+{
+ "contributes": {
+ "configuration": {
+ "type": "object",
+ "title:": "Solang Solidity Compiler",
+ "properties": {
+ "solang.target": {
+ "scope": "window",
+ "type": "string",
+ "enum": [
+ "solana",
+ "polkadot",
+ "evm"
+ ],
+ "default": "solana",
+ "description": "Chain to build for. The Solidity language changes in subtle ways depending on the target."
+ },
+ "solang.updates.askBeforeDownload": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether to ask for permission before downloading any files from the Internet"
+ },
+ "solidity.trace.server": {
+ "scope": "window",
+ "type": "string",
+ "enum": [
+ "off",
+ "messages",
+ "verbose"
+ ],
+ "default": "verbose"
+ }
+ }
+ },
+ "capabilities": {
+ "hoverProvider": "true",
+ "formatting": {
+ "dynamicRegistration": true
+ }
+ },
+ "languages": [
+ {
+ "id": "solidity",
+ "aliases": [
+ "Solidity",
+ "solidity"
+ ],
+ "extensions": [
+ ".sol"
+ ],
+ "configuration": "./language_configuration/solidity.configuration.json"
+ }
+ ],
+ "snippets": [
+ {
+ "language": "solidity",
+ "path": "./snippets_solidity.json"
+ }
+ ],
+ "grammars": [
+ {
+ "language": "solidity",
+ "scopeName": "source.solidity",
+ "path": "./syntaxes_solidity.json"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/frontend/src/lib/editor/language/index.ts b/packages/frontend/src/lib/editor/language/index.ts
new file mode 100644
index 0000000..ecc38fe
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/index.ts
@@ -0,0 +1,268 @@
+import * as monaco from "monaco-editor";
+// import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from "";
+import contributes from "./contributes.json";
+import { solidityTokensProvider, solidityLanguageConfig } from "./solidity_syntax";
+import * as proto from "vscode-languageserver-protocol";
+import { Monaco } from "@monaco-editor/react";
+import Client from "../client";
+import { monacoToProtocol, protocolToMonaco } from "../utils";
+
+const themeType = "vs-dark";
+const themeName = "remix-dark";
+const formatColor = (name: string): string => {
+ let color = window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
+ if (color.length === 4) {
+ color = color.concat(color.substr(1));
+ }
+ console.log("color ", color);
+ return color;
+};
+// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors
+const lightColor = formatColor("--light");
+const infoColor = formatColor("--info");
+const darkColor = formatColor("--dark");
+const secondaryColor = formatColor("--secondary");
+const primaryColor = formatColor("--primary");
+const textColor = formatColor("--text") || darkColor;
+const textbackground = formatColor("--text-background") || lightColor;
+
+const blueColor = formatColor("--blue");
+const successColor = formatColor("--success");
+const warningColor = formatColor("--warning");
+const yellowColor = formatColor("--yellow");
+const pinkColor = formatColor("--pink");
+const locationColor = "#9e7e08";
+// const purpleColor = formatColor('--purple')
+const dangerColor = formatColor("--danger");
+const greenColor = formatColor("--green");
+const orangeColor = formatColor("--orange");
+const grayColor = formatColor("--gray");
+
+// export const protocolToMonaco = new ProtocolToMonacoConverter(monaco);
+// const monacoToProtocol = new MonacoToProtocolConverter(monaco);
+
+let language: null | Language;
+
+export default class Language implements monaco.languages.ILanguageExtensionPoint {
+ readonly id: string;
+ readonly aliases: string[];
+ readonly extensions: string[];
+ readonly mimetypes: string[];
+
+ private constructor(client: Client, monaco: Monaco) {
+ const { id, aliases, extensions, mimetypes } = Language.extensionPoint();
+ this.id = id;
+ this.aliases = aliases;
+ this.extensions = extensions;
+ this.mimetypes = mimetypes;
+ this.registerLanguage(client, monaco);
+ }
+
+ static extensionPoint(): monaco.languages.ILanguageExtensionPoint & {
+ aliases: string[];
+ extensions: string[];
+ mimetypes: string[];
+ } {
+ const id = contributes.contributes.languages[0].id;
+ const aliases = contributes.contributes.languages[0].aliases;
+ const extensions = contributes.contributes.languages[0].extensions;
+ const mimetypes = ["text/x-solidity"]; // This is a common MIME type for Solidity, but you may need to adjust it
+
+ return { id, extensions, aliases, mimetypes };
+ }
+
+ private registerLanguage(client: Client, monaco: Monaco): void {
+ void client;
+ monaco.languages.register({ id: "solidity" });
+
+ monaco.languages.setMonarchTokensProvider("solidity", solidityTokensProvider as any);
+ monaco.languages.setLanguageConfiguration("solidity", solidityLanguageConfig as any);
+
+ monaco.languages.registerCompletionItemProvider(this.id, {
+ async provideCompletionItems(model, position, context, token): Promise {
+ void token;
+ const response = await (client.request(proto.CompletionRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ position: monacoToProtocol.asPosition(position.column, position.lineNumber),
+ context: monacoToProtocol.asCompletionContext(context),
+ } as proto.CompletionParams) as Promise);
+ console.log(response);
+
+ const word = model.getWordUntilPosition(position);
+ const result: monaco.languages.CompletionList = protocolToMonaco.asCompletionResult(response, {
+ startLineNumber: position.lineNumber,
+ startColumn: word.startColumn,
+ endLineNumber: position.lineNumber,
+ endColumn: word.endColumn,
+ });
+
+ return result;
+ },
+ });
+
+ monaco.editor.defineTheme(themeName, {
+ base: themeType,
+ inherit: true, // can also be false to completely replace the builtin rules
+ rules: [
+ // global variables
+ { token: "keyword.abi", foreground: blueColor },
+ { token: "keyword.block", foreground: blueColor },
+ { token: "keyword.bytes", foreground: blueColor },
+ { token: "keyword.msg", foreground: blueColor },
+ { token: "keyword.tx", foreground: blueColor },
+
+ // global functions
+ { token: "keyword.assert", foreground: blueColor },
+ { token: "keyword.require", foreground: blueColor },
+ { token: "keyword.revert", foreground: blueColor },
+ { token: "keyword.blockhash", foreground: blueColor },
+ { token: "keyword.keccak256", foreground: blueColor },
+ { token: "keyword.sha256", foreground: blueColor },
+ { token: "keyword.ripemd160", foreground: blueColor },
+ { token: "keyword.ecrecover", foreground: blueColor },
+ { token: "keyword.addmod", foreground: blueColor },
+ { token: "keyword.mulmod", foreground: blueColor },
+ { token: "keyword.selfdestruct", foreground: blueColor },
+ { token: "keyword.type ", foreground: blueColor },
+ { token: "keyword.gasleft", foreground: blueColor },
+
+ // specials
+ { token: "keyword.super", foreground: infoColor },
+ { token: "keyword.this", foreground: infoColor },
+ { token: "keyword.virtual", foreground: infoColor },
+
+ // for state variables
+ { token: "keyword.constants", foreground: grayColor },
+ { token: "keyword.override", foreground: grayColor },
+ { token: "keyword.immutable", foreground: grayColor },
+
+ // data location
+ { token: "keyword.memory", foreground: locationColor },
+ { token: "keyword.storage", foreground: locationColor },
+ { token: "keyword.calldata", foreground: locationColor },
+
+ // for Events
+ { token: "keyword.indexed", foreground: yellowColor },
+ { token: "keyword.anonymous", foreground: yellowColor },
+
+ // for functions
+ { token: "keyword.external", foreground: successColor },
+ { token: "keyword.internal", foreground: successColor },
+ { token: "keyword.private", foreground: successColor },
+ { token: "keyword.public", foreground: successColor },
+ { token: "keyword.view", foreground: successColor },
+ { token: "keyword.pure", foreground: successColor },
+ { token: "keyword.payable", foreground: successColor },
+ { token: "keyword.nonpayable", foreground: successColor },
+
+ // Errors
+ { token: "keyword.Error", foreground: dangerColor },
+ { token: "keyword.Panic", foreground: dangerColor },
+
+ // special functions
+ { token: "keyword.fallback", foreground: pinkColor },
+ { token: "keyword.receive", foreground: pinkColor },
+ { token: "keyword.constructor", foreground: pinkColor },
+
+ // identifiers
+ { token: "keyword.identifier", foreground: warningColor },
+ { token: "keyword.for", foreground: warningColor },
+ { token: "keyword.break", foreground: warningColor },
+ { token: "keyword.continue", foreground: warningColor },
+ { token: "keyword.while", foreground: warningColor },
+ { token: "keyword.do", foreground: warningColor },
+ { token: "keyword.delete", foreground: warningColor },
+
+ { token: "keyword.if", foreground: yellowColor },
+ { token: "keyword.else", foreground: yellowColor },
+
+ { token: "keyword.throw", foreground: orangeColor },
+ { token: "keyword.catch", foreground: orangeColor },
+ { token: "keyword.try", foreground: orangeColor },
+
+ // returns
+ { token: "keyword.returns", foreground: greenColor },
+ { token: "keyword.return", foreground: greenColor },
+ ],
+ colors: {},
+ });
+ monaco.editor.setTheme(themeName);
+
+ monaco.languages.registerDocumentSymbolProvider(this.id, {
+ // eslint-disable-next-line
+ async provideDocumentSymbols(model, token): Promise {
+ void token;
+ const response = await (client.request(proto.DocumentSymbolRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ } as proto.DocumentSymbolParams) as Promise);
+
+ console.log({ response });
+
+ const uri = model.uri;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const result: monaco.languages.DocumentSymbol[] = protocolToMonaco.asSymbolInformations(response, uri);
+
+ return result;
+ },
+ });
+
+ monaco.languages.registerHoverProvider(this.id, {
+ // eslint-disable-next-line
+ async provideHover(model, position, token): Promise {
+ void token;
+ const response = await (client.request(proto.HoverRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ position: monacoToProtocol.asPosition(position.column, position.lineNumber),
+ } as proto.HoverParams) as Promise);
+ console.log(response);
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const result: monaco.languages.Hover = protocolToMonaco.asHover(response);
+
+ console.log("Hover result: ", result);
+
+ // add handler if hover result is null
+ let message = "";
+ if (result == null) {
+ message = "";
+ } else {
+ message = result.contents[0].value;
+ }
+
+ // Create a decoration with the hover result
+ const decoration: monaco.editor.IModelDeltaDecoration = {
+ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
+ options: {
+ hoverMessage: { value: message },
+ },
+ };
+
+ // Apply the decoration to the editor
+ model.deltaDecorations([], [decoration]);
+
+ return result;
+ },
+ });
+
+ monaco.editor.onDidCreateModel((model) => {
+ setTimeout(() => {
+ console.log("content changed", event);
+ const diagnostic = client.diagnostic;
+
+ const markers = protocolToMonaco.asDiagnostics(diagnostic.diagnostics);
+
+ monaco.editor.setModelMarkers(model, "solidity", markers);
+ }, 500);
+ });
+ }
+
+ static initialize(client: Client, monaco: Monaco): Language {
+ if (null == language) {
+ language = new Language(client, monaco);
+ } else {
+ console.warn("Language already initialized; ignoring");
+ }
+ return language;
+ }
+}
diff --git a/packages/frontend/src/lib/editor/language/snippets_solidity.json b/packages/frontend/src/lib/editor/language/snippets_solidity.json
new file mode 100644
index 0000000..548a669
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/snippets_solidity.json
@@ -0,0 +1,65 @@
+{
+ ".source.solidity": {
+ "pragma solidity":{
+ "prefix": "pra",
+ "body": "pragma solidity ${1:version};"
+ },
+ "import contract": {
+ "prefix": "im",
+ "body": "import '${1:contract}';"
+ },
+ "contract declaration": {
+ "prefix": "con",
+ "body": "contract ${1:Name} {\n\t$0\n}"
+ },
+ "library declaration": {
+ "prefix": "lib",
+ "body": "library ${1:Name} {\n\t$0\n}"
+ },
+ "interface declaration": {
+ "prefix": "interf",
+ "body": "interface ${1:Name} {\n\t$0\n}"
+ },
+ "enum declaration": {
+ "prefix": "enum",
+ "body": "enum ${1:Name} {${2:item1}, ${3:item2} }"
+ },
+ "mapping declaration":{
+ "prefix": "map",
+ "body": "mapping (${1:type1}=>${2:type2}) ${3:name};"
+ },
+ "constructor declaration": {
+ "prefix": "const",
+ "body": "constructor (${1:type} ${2:name}) public {\n\t$0\n}"
+ },
+ "function declaration": {
+ "prefix": "func",
+ "body": "function ${1:name}(${2:type} ${3:name}) {\n\t$0\n}"
+ },
+ "function return declaration": {
+ "prefix": "funcr",
+ "body": "function ${1:name}(${2:type} ${3:name}) returns (${4:type} ${5:name}) {\n\t$0\n}"
+ }
+ ,
+ "function view declaration": {
+ "prefix": "funcrview",
+ "body": "function ${1:name}(${2:type} ${3:name}) view public returns (${4:type} ${5:name}) {\n\t$0\n}"
+ },
+ "event declaration": {
+ "prefix": "ev",
+ "body": "event ${1:name}(${2:type} ${3:name} $0);"
+ },
+ "modifier declaration": {
+ "prefix": "mod",
+ "body": "modifier ${1:name}($2) {\n\t$0_\n}"
+ },
+ "if else statement": {
+ "prefix": "ife",
+ "body": "if (${1:condition}) {\n\t$2\n} else {\n\t$0\n}"
+ },
+ "for statement": {
+ "prefix": "for",
+ "body": "for (var ${1:index} = 0; $1 < ${2:array}.length; $1${3:++}) {\n\t$0\n}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/frontend/src/lib/editor/language/solidity.configuration.json b/packages/frontend/src/lib/editor/language/solidity.configuration.json
new file mode 100644
index 0000000..b336454
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/solidity.configuration.json
@@ -0,0 +1,23 @@
+{
+ "comments": {
+ "lineComment": "//",
+
+ "blockComment": [ "/*", "*/" ]
+ },
+ "brackets": [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"]
+ ],
+ "autoClosingPairs": [
+ { "open": "{", "close": "}" },
+ { "open": "[", "close": "]" },
+ { "open": "(", "close": ")" },
+ { "open": "/**", "close": " */", "notIn": ["string"] }
+ ],
+ "surroundingPairs": [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"]
+ ]
+}
\ No newline at end of file
diff --git a/packages/frontend/src/lib/editor/language/solidity_syntax.ts b/packages/frontend/src/lib/editor/language/solidity_syntax.ts
new file mode 100644
index 0000000..d358833
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/solidity_syntax.ts
@@ -0,0 +1,1422 @@
+/* eslint-disable */
+export const solidityLanguageConfig = {
+ comments: {
+ lineComment: '//',
+ blockComment: ['/*', '*/']
+ },
+ brackets: [
+ ['{', '}'],
+ ['[', ']'],
+ ['(', ')'],
+ ['<', '>']
+ ],
+ autoClosingPairs: [
+ { open: '"', close: '"', notIn: ['string', 'comment'] },
+ { open: "'", close: "'", notIn: ['string', 'comment'] },
+ { open: '{', close: '}', notIn: ['string', 'comment'] },
+ { open: '[', close: ']', notIn: ['string', 'comment'] },
+ { open: '(', close: ')', notIn: ['string', 'comment'] }
+ ]
+}
+
+export const solidityTokensProvider = {
+ defaultToken: '',
+ tokenPostfix: '.sol',
+
+ brackets: [
+ { token: 'delimiter.curly', open: '{', close: '}' },
+ { token: 'delimiter.parenthesis', open: '(', close: ')' },
+ { token: 'delimiter.square', open: '[', close: ']' },
+ { token: 'delimiter.angle', open: '<', close: '>' }
+ ],
+
+ keywords: [
+ // Main keywords
+ 'pragma',
+ 'solidity',
+ 'contract',
+ 'library',
+ 'using',
+ 'struct',
+ 'function',
+ 'modifier',
+ 'constructor',
+ // Built-in types
+ 'address',
+ 'string',
+ 'bool',
+ // Other types
+ 'Int',
+ 'Uint',
+ 'Byte',
+ 'Fixed',
+ 'Ufixed',
+ // All int
+ 'int',
+ 'int8',
+ 'int16',
+ 'int24',
+ 'int32',
+ 'int40',
+ 'int48',
+ 'int56',
+ 'int64',
+ 'int72',
+ 'int80',
+ 'int88',
+ 'int96',
+ 'int104',
+ 'int112',
+ 'int120',
+ 'int128',
+ 'int136',
+ 'int144',
+ 'int152',
+ 'int160',
+ 'int168',
+ 'int176',
+ 'int184',
+ 'int192',
+ 'int200',
+ 'int208',
+ 'int216',
+ 'int224',
+ 'int232',
+ 'int240',
+ 'int248',
+ 'int256',
+ // All uint
+ 'uint',
+ 'uint8',
+ 'uint16',
+ 'uint24',
+ 'uint32',
+ 'uint40',
+ 'uint48',
+ 'uint56',
+ 'uint64',
+ 'uint72',
+ 'uint80',
+ 'uint88',
+ 'uint96',
+ 'uint104',
+ 'uint112',
+ 'uint120',
+ 'uint128',
+ 'uint136',
+ 'uint144',
+ 'uint152',
+ 'uint160',
+ 'uint168',
+ 'uint176',
+ 'uint184',
+ 'uint192',
+ 'uint200',
+ 'uint208',
+ 'uint216',
+ 'uint224',
+ 'uint232',
+ 'uint240',
+ 'uint248',
+ 'uint256',
+ // All Byte
+ 'byte',
+ 'bytes',
+ 'bytes1',
+ 'bytes2',
+ 'bytes3',
+ 'bytes4',
+ 'bytes5',
+ 'bytes6',
+ 'bytes7',
+ 'bytes8',
+ 'bytes9',
+ 'bytes10',
+ 'bytes11',
+ 'bytes12',
+ 'bytes13',
+ 'bytes14',
+ 'bytes15',
+ 'bytes16',
+ 'bytes17',
+ 'bytes18',
+ 'bytes19',
+ 'bytes20',
+ 'bytes21',
+ 'bytes22',
+ 'bytes23',
+ 'bytes24',
+ 'bytes25',
+ 'bytes26',
+ 'bytes27',
+ 'bytes28',
+ 'bytes29',
+ 'bytes30',
+ 'bytes31',
+ 'bytes32',
+ // All fixed
+ 'fixed',
+ 'fixed0x8',
+ 'fixed0x16',
+ 'fixed0x24',
+ 'fixed0x32',
+ 'fixed0x40',
+ 'fixed0x48',
+ 'fixed0x56',
+ 'fixed0x64',
+ 'fixed0x72',
+ 'fixed0x80',
+ 'fixed0x88',
+ 'fixed0x96',
+ 'fixed0x104',
+ 'fixed0x112',
+ 'fixed0x120',
+ 'fixed0x128',
+ 'fixed0x136',
+ 'fixed0x144',
+ 'fixed0x152',
+ 'fixed0x160',
+ 'fixed0x168',
+ 'fixed0x176',
+ 'fixed0x184',
+ 'fixed0x192',
+ 'fixed0x200',
+ 'fixed0x208',
+ 'fixed0x216',
+ 'fixed0x224',
+ 'fixed0x232',
+ 'fixed0x240',
+ 'fixed0x248',
+ 'fixed0x256',
+ 'fixed8x8',
+ 'fixed8x16',
+ 'fixed8x24',
+ 'fixed8x32',
+ 'fixed8x40',
+ 'fixed8x48',
+ 'fixed8x56',
+ 'fixed8x64',
+ 'fixed8x72',
+ 'fixed8x80',
+ 'fixed8x88',
+ 'fixed8x96',
+ 'fixed8x104',
+ 'fixed8x112',
+ 'fixed8x120',
+ 'fixed8x128',
+ 'fixed8x136',
+ 'fixed8x144',
+ 'fixed8x152',
+ 'fixed8x160',
+ 'fixed8x168',
+ 'fixed8x176',
+ 'fixed8x184',
+ 'fixed8x192',
+ 'fixed8x200',
+ 'fixed8x208',
+ 'fixed8x216',
+ 'fixed8x224',
+ 'fixed8x232',
+ 'fixed8x240',
+ 'fixed8x248',
+ 'fixed16x8',
+ 'fixed16x16',
+ 'fixed16x24',
+ 'fixed16x32',
+ 'fixed16x40',
+ 'fixed16x48',
+ 'fixed16x56',
+ 'fixed16x64',
+ 'fixed16x72',
+ 'fixed16x80',
+ 'fixed16x88',
+ 'fixed16x96',
+ 'fixed16x104',
+ 'fixed16x112',
+ 'fixed16x120',
+ 'fixed16x128',
+ 'fixed16x136',
+ 'fixed16x144',
+ 'fixed16x152',
+ 'fixed16x160',
+ 'fixed16x168',
+ 'fixed16x176',
+ 'fixed16x184',
+ 'fixed16x192',
+ 'fixed16x200',
+ 'fixed16x208',
+ 'fixed16x216',
+ 'fixed16x224',
+ 'fixed16x232',
+ 'fixed16x240',
+ 'fixed24x8',
+ 'fixed24x16',
+ 'fixed24x24',
+ 'fixed24x32',
+ 'fixed24x40',
+ 'fixed24x48',
+ 'fixed24x56',
+ 'fixed24x64',
+ 'fixed24x72',
+ 'fixed24x80',
+ 'fixed24x88',
+ 'fixed24x96',
+ 'fixed24x104',
+ 'fixed24x112',
+ 'fixed24x120',
+ 'fixed24x128',
+ 'fixed24x136',
+ 'fixed24x144',
+ 'fixed24x152',
+ 'fixed24x160',
+ 'fixed24x168',
+ 'fixed24x176',
+ 'fixed24x184',
+ 'fixed24x192',
+ 'fixed24x200',
+ 'fixed24x208',
+ 'fixed24x216',
+ 'fixed24x224',
+ 'fixed24x232',
+ 'fixed32x8',
+ 'fixed32x16',
+ 'fixed32x24',
+ 'fixed32x32',
+ 'fixed32x40',
+ 'fixed32x48',
+ 'fixed32x56',
+ 'fixed32x64',
+ 'fixed32x72',
+ 'fixed32x80',
+ 'fixed32x88',
+ 'fixed32x96',
+ 'fixed32x104',
+ 'fixed32x112',
+ 'fixed32x120',
+ 'fixed32x128',
+ 'fixed32x136',
+ 'fixed32x144',
+ 'fixed32x152',
+ 'fixed32x160',
+ 'fixed32x168',
+ 'fixed32x176',
+ 'fixed32x184',
+ 'fixed32x192',
+ 'fixed32x200',
+ 'fixed32x208',
+ 'fixed32x216',
+ 'fixed32x224',
+ 'fixed40x8',
+ 'fixed40x16',
+ 'fixed40x24',
+ 'fixed40x32',
+ 'fixed40x40',
+ 'fixed40x48',
+ 'fixed40x56',
+ 'fixed40x64',
+ 'fixed40x72',
+ 'fixed40x80',
+ 'fixed40x88',
+ 'fixed40x96',
+ 'fixed40x104',
+ 'fixed40x112',
+ 'fixed40x120',
+ 'fixed40x128',
+ 'fixed40x136',
+ 'fixed40x144',
+ 'fixed40x152',
+ 'fixed40x160',
+ 'fixed40x168',
+ 'fixed40x176',
+ 'fixed40x184',
+ 'fixed40x192',
+ 'fixed40x200',
+ 'fixed40x208',
+ 'fixed40x216',
+ 'fixed48x8',
+ 'fixed48x16',
+ 'fixed48x24',
+ 'fixed48x32',
+ 'fixed48x40',
+ 'fixed48x48',
+ 'fixed48x56',
+ 'fixed48x64',
+ 'fixed48x72',
+ 'fixed48x80',
+ 'fixed48x88',
+ 'fixed48x96',
+ 'fixed48x104',
+ 'fixed48x112',
+ 'fixed48x120',
+ 'fixed48x128',
+ 'fixed48x136',
+ 'fixed48x144',
+ 'fixed48x152',
+ 'fixed48x160',
+ 'fixed48x168',
+ 'fixed48x176',
+ 'fixed48x184',
+ 'fixed48x192',
+ 'fixed48x200',
+ 'fixed48x208',
+ 'fixed56x8',
+ 'fixed56x16',
+ 'fixed56x24',
+ 'fixed56x32',
+ 'fixed56x40',
+ 'fixed56x48',
+ 'fixed56x56',
+ 'fixed56x64',
+ 'fixed56x72',
+ 'fixed56x80',
+ 'fixed56x88',
+ 'fixed56x96',
+ 'fixed56x104',
+ 'fixed56x112',
+ 'fixed56x120',
+ 'fixed56x128',
+ 'fixed56x136',
+ 'fixed56x144',
+ 'fixed56x152',
+ 'fixed56x160',
+ 'fixed56x168',
+ 'fixed56x176',
+ 'fixed56x184',
+ 'fixed56x192',
+ 'fixed56x200',
+ 'fixed64x8',
+ 'fixed64x16',
+ 'fixed64x24',
+ 'fixed64x32',
+ 'fixed64x40',
+ 'fixed64x48',
+ 'fixed64x56',
+ 'fixed64x64',
+ 'fixed64x72',
+ 'fixed64x80',
+ 'fixed64x88',
+ 'fixed64x96',
+ 'fixed64x104',
+ 'fixed64x112',
+ 'fixed64x120',
+ 'fixed64x128',
+ 'fixed64x136',
+ 'fixed64x144',
+ 'fixed64x152',
+ 'fixed64x160',
+ 'fixed64x168',
+ 'fixed64x176',
+ 'fixed64x184',
+ 'fixed64x192',
+ 'fixed72x8',
+ 'fixed72x16',
+ 'fixed72x24',
+ 'fixed72x32',
+ 'fixed72x40',
+ 'fixed72x48',
+ 'fixed72x56',
+ 'fixed72x64',
+ 'fixed72x72',
+ 'fixed72x80',
+ 'fixed72x88',
+ 'fixed72x96',
+ 'fixed72x104',
+ 'fixed72x112',
+ 'fixed72x120',
+ 'fixed72x128',
+ 'fixed72x136',
+ 'fixed72x144',
+ 'fixed72x152',
+ 'fixed72x160',
+ 'fixed72x168',
+ 'fixed72x176',
+ 'fixed72x184',
+ 'fixed80x8',
+ 'fixed80x16',
+ 'fixed80x24',
+ 'fixed80x32',
+ 'fixed80x40',
+ 'fixed80x48',
+ 'fixed80x56',
+ 'fixed80x64',
+ 'fixed80x72',
+ 'fixed80x80',
+ 'fixed80x88',
+ 'fixed80x96',
+ 'fixed80x104',
+ 'fixed80x112',
+ 'fixed80x120',
+ 'fixed80x128',
+ 'fixed80x136',
+ 'fixed80x144',
+ 'fixed80x152',
+ 'fixed80x160',
+ 'fixed80x168',
+ 'fixed80x176',
+ 'fixed88x8',
+ 'fixed88x16',
+ 'fixed88x24',
+ 'fixed88x32',
+ 'fixed88x40',
+ 'fixed88x48',
+ 'fixed88x56',
+ 'fixed88x64',
+ 'fixed88x72',
+ 'fixed88x80',
+ 'fixed88x88',
+ 'fixed88x96',
+ 'fixed88x104',
+ 'fixed88x112',
+ 'fixed88x120',
+ 'fixed88x128',
+ 'fixed88x136',
+ 'fixed88x144',
+ 'fixed88x152',
+ 'fixed88x160',
+ 'fixed88x168',
+ 'fixed96x8',
+ 'fixed96x16',
+ 'fixed96x24',
+ 'fixed96x32',
+ 'fixed96x40',
+ 'fixed96x48',
+ 'fixed96x56',
+ 'fixed96x64',
+ 'fixed96x72',
+ 'fixed96x80',
+ 'fixed96x88',
+ 'fixed96x96',
+ 'fixed96x104',
+ 'fixed96x112',
+ 'fixed96x120',
+ 'fixed96x128',
+ 'fixed96x136',
+ 'fixed96x144',
+ 'fixed96x152',
+ 'fixed96x160',
+ 'fixed104x8',
+ 'fixed104x16',
+ 'fixed104x24',
+ 'fixed104x32',
+ 'fixed104x40',
+ 'fixed104x48',
+ 'fixed104x56',
+ 'fixed104x64',
+ 'fixed104x72',
+ 'fixed104x80',
+ 'fixed104x88',
+ 'fixed104x96',
+ 'fixed104x104',
+ 'fixed104x112',
+ 'fixed104x120',
+ 'fixed104x128',
+ 'fixed104x136',
+ 'fixed104x144',
+ 'fixed104x152',
+ 'fixed112x8',
+ 'fixed112x16',
+ 'fixed112x24',
+ 'fixed112x32',
+ 'fixed112x40',
+ 'fixed112x48',
+ 'fixed112x56',
+ 'fixed112x64',
+ 'fixed112x72',
+ 'fixed112x80',
+ 'fixed112x88',
+ 'fixed112x96',
+ 'fixed112x104',
+ 'fixed112x112',
+ 'fixed112x120',
+ 'fixed112x128',
+ 'fixed112x136',
+ 'fixed112x144',
+ 'fixed120x8',
+ 'fixed120x16',
+ 'fixed120x24',
+ 'fixed120x32',
+ 'fixed120x40',
+ 'fixed120x48',
+ 'fixed120x56',
+ 'fixed120x64',
+ 'fixed120x72',
+ 'fixed120x80',
+ 'fixed120x88',
+ 'fixed120x96',
+ 'fixed120x104',
+ 'fixed120x112',
+ 'fixed120x120',
+ 'fixed120x128',
+ 'fixed120x136',
+ 'fixed128x8',
+ 'fixed128x16',
+ 'fixed128x24',
+ 'fixed128x32',
+ 'fixed128x40',
+ 'fixed128x48',
+ 'fixed128x56',
+ 'fixed128x64',
+ 'fixed128x72',
+ 'fixed128x80',
+ 'fixed128x88',
+ 'fixed128x96',
+ 'fixed128x104',
+ 'fixed128x112',
+ 'fixed128x120',
+ 'fixed128x128',
+ 'fixed136x8',
+ 'fixed136x16',
+ 'fixed136x24',
+ 'fixed136x32',
+ 'fixed136x40',
+ 'fixed136x48',
+ 'fixed136x56',
+ 'fixed136x64',
+ 'fixed136x72',
+ 'fixed136x80',
+ 'fixed136x88',
+ 'fixed136x96',
+ 'fixed136x104',
+ 'fixed136x112',
+ 'fixed136x120',
+ 'fixed144x8',
+ 'fixed144x16',
+ 'fixed144x24',
+ 'fixed144x32',
+ 'fixed144x40',
+ 'fixed144x48',
+ 'fixed144x56',
+ 'fixed144x64',
+ 'fixed144x72',
+ 'fixed144x80',
+ 'fixed144x88',
+ 'fixed144x96',
+ 'fixed144x104',
+ 'fixed144x112',
+ 'fixed152x8',
+ 'fixed152x16',
+ 'fixed152x24',
+ 'fixed152x32',
+ 'fixed152x40',
+ 'fixed152x48',
+ 'fixed152x56',
+ 'fixed152x64',
+ 'fixed152x72',
+ 'fixed152x80',
+ 'fixed152x88',
+ 'fixed152x96',
+ 'fixed152x104',
+ 'fixed160x8',
+ 'fixed160x16',
+ 'fixed160x24',
+ 'fixed160x32',
+ 'fixed160x40',
+ 'fixed160x48',
+ 'fixed160x56',
+ 'fixed160x64',
+ 'fixed160x72',
+ 'fixed160x80',
+ 'fixed160x88',
+ 'fixed160x96',
+ 'fixed168x8',
+ 'fixed168x16',
+ 'fixed168x24',
+ 'fixed168x32',
+ 'fixed168x40',
+ 'fixed168x48',
+ 'fixed168x56',
+ 'fixed168x64',
+ 'fixed168x72',
+ 'fixed168x80',
+ 'fixed168x88',
+ 'fixed176x8',
+ 'fixed176x16',
+ 'fixed176x24',
+ 'fixed176x32',
+ 'fixed176x40',
+ 'fixed176x48',
+ 'fixed176x56',
+ 'fixed176x64',
+ 'fixed176x72',
+ 'fixed176x80',
+ 'fixed184x8',
+ 'fixed184x16',
+ 'fixed184x24',
+ 'fixed184x32',
+ 'fixed184x40',
+ 'fixed184x48',
+ 'fixed184x56',
+ 'fixed184x64',
+ 'fixed184x72',
+ 'fixed192x8',
+ 'fixed192x16',
+ 'fixed192x24',
+ 'fixed192x32',
+ 'fixed192x40',
+ 'fixed192x48',
+ 'fixed192x56',
+ 'fixed192x64',
+ 'fixed200x8',
+ 'fixed200x16',
+ 'fixed200x24',
+ 'fixed200x32',
+ 'fixed200x40',
+ 'fixed200x48',
+ 'fixed200x56',
+ 'fixed208x8',
+ 'fixed208x16',
+ 'fixed208x24',
+ 'fixed208x32',
+ 'fixed208x40',
+ 'fixed208x48',
+ 'fixed216x8',
+ 'fixed216x16',
+ 'fixed216x24',
+ 'fixed216x32',
+ 'fixed216x40',
+ 'fixed224x8',
+ 'fixed224x16',
+ 'fixed224x24',
+ 'fixed224x32',
+ 'fixed232x8',
+ 'fixed232x16',
+ 'fixed232x24',
+ 'fixed240x8',
+ 'fixed240x16',
+ 'fixed248x8',
+ // All ufixed
+ 'ufixed',
+ 'ufixed0x8',
+ 'ufixed0x16',
+ 'ufixed0x24',
+ 'ufixed0x32',
+ 'ufixed0x40',
+ 'ufixed0x48',
+ 'ufixed0x56',
+ 'ufixed0x64',
+ 'ufixed0x72',
+ 'ufixed0x80',
+ 'ufixed0x88',
+ 'ufixed0x96',
+ 'ufixed0x104',
+ 'ufixed0x112',
+ 'ufixed0x120',
+ 'ufixed0x128',
+ 'ufixed0x136',
+ 'ufixed0x144',
+ 'ufixed0x152',
+ 'ufixed0x160',
+ 'ufixed0x168',
+ 'ufixed0x176',
+ 'ufixed0x184',
+ 'ufixed0x192',
+ 'ufixed0x200',
+ 'ufixed0x208',
+ 'ufixed0x216',
+ 'ufixed0x224',
+ 'ufixed0x232',
+ 'ufixed0x240',
+ 'ufixed0x248',
+ 'ufixed0x256',
+ 'ufixed8x8',
+ 'ufixed8x16',
+ 'ufixed8x24',
+ 'ufixed8x32',
+ 'ufixed8x40',
+ 'ufixed8x48',
+ 'ufixed8x56',
+ 'ufixed8x64',
+ 'ufixed8x72',
+ 'ufixed8x80',
+ 'ufixed8x88',
+ 'ufixed8x96',
+ 'ufixed8x104',
+ 'ufixed8x112',
+ 'ufixed8x120',
+ 'ufixed8x128',
+ 'ufixed8x136',
+ 'ufixed8x144',
+ 'ufixed8x152',
+ 'ufixed8x160',
+ 'ufixed8x168',
+ 'ufixed8x176',
+ 'ufixed8x184',
+ 'ufixed8x192',
+ 'ufixed8x200',
+ 'ufixed8x208',
+ 'ufixed8x216',
+ 'ufixed8x224',
+ 'ufixed8x232',
+ 'ufixed8x240',
+ 'ufixed8x248',
+ 'ufixed16x8',
+ 'ufixed16x16',
+ 'ufixed16x24',
+ 'ufixed16x32',
+ 'ufixed16x40',
+ 'ufixed16x48',
+ 'ufixed16x56',
+ 'ufixed16x64',
+ 'ufixed16x72',
+ 'ufixed16x80',
+ 'ufixed16x88',
+ 'ufixed16x96',
+ 'ufixed16x104',
+ 'ufixed16x112',
+ 'ufixed16x120',
+ 'ufixed16x128',
+ 'ufixed16x136',
+ 'ufixed16x144',
+ 'ufixed16x152',
+ 'ufixed16x160',
+ 'ufixed16x168',
+ 'ufixed16x176',
+ 'ufixed16x184',
+ 'ufixed16x192',
+ 'ufixed16x200',
+ 'ufixed16x208',
+ 'ufixed16x216',
+ 'ufixed16x224',
+ 'ufixed16x232',
+ 'ufixed16x240',
+ 'ufixed24x8',
+ 'ufixed24x16',
+ 'ufixed24x24',
+ 'ufixed24x32',
+ 'ufixed24x40',
+ 'ufixed24x48',
+ 'ufixed24x56',
+ 'ufixed24x64',
+ 'ufixed24x72',
+ 'ufixed24x80',
+ 'ufixed24x88',
+ 'ufixed24x96',
+ 'ufixed24x104',
+ 'ufixed24x112',
+ 'ufixed24x120',
+ 'ufixed24x128',
+ 'ufixed24x136',
+ 'ufixed24x144',
+ 'ufixed24x152',
+ 'ufixed24x160',
+ 'ufixed24x168',
+ 'ufixed24x176',
+ 'ufixed24x184',
+ 'ufixed24x192',
+ 'ufixed24x200',
+ 'ufixed24x208',
+ 'ufixed24x216',
+ 'ufixed24x224',
+ 'ufixed24x232',
+ 'ufixed32x8',
+ 'ufixed32x16',
+ 'ufixed32x24',
+ 'ufixed32x32',
+ 'ufixed32x40',
+ 'ufixed32x48',
+ 'ufixed32x56',
+ 'ufixed32x64',
+ 'ufixed32x72',
+ 'ufixed32x80',
+ 'ufixed32x88',
+ 'ufixed32x96',
+ 'ufixed32x104',
+ 'ufixed32x112',
+ 'ufixed32x120',
+ 'ufixed32x128',
+ 'ufixed32x136',
+ 'ufixed32x144',
+ 'ufixed32x152',
+ 'ufixed32x160',
+ 'ufixed32x168',
+ 'ufixed32x176',
+ 'ufixed32x184',
+ 'ufixed32x192',
+ 'ufixed32x200',
+ 'ufixed32x208',
+ 'ufixed32x216',
+ 'ufixed32x224',
+ 'ufixed40x8',
+ 'ufixed40x16',
+ 'ufixed40x24',
+ 'ufixed40x32',
+ 'ufixed40x40',
+ 'ufixed40x48',
+ 'ufixed40x56',
+ 'ufixed40x64',
+ 'ufixed40x72',
+ 'ufixed40x80',
+ 'ufixed40x88',
+ 'ufixed40x96',
+ 'ufixed40x104',
+ 'ufixed40x112',
+ 'ufixed40x120',
+ 'ufixed40x128',
+ 'ufixed40x136',
+ 'ufixed40x144',
+ 'ufixed40x152',
+ 'ufixed40x160',
+ 'ufixed40x168',
+ 'ufixed40x176',
+ 'ufixed40x184',
+ 'ufixed40x192',
+ 'ufixed40x200',
+ 'ufixed40x208',
+ 'ufixed40x216',
+ 'ufixed48x8',
+ 'ufixed48x16',
+ 'ufixed48x24',
+ 'ufixed48x32',
+ 'ufixed48x40',
+ 'ufixed48x48',
+ 'ufixed48x56',
+ 'ufixed48x64',
+ 'ufixed48x72',
+ 'ufixed48x80',
+ 'ufixed48x88',
+ 'ufixed48x96',
+ 'ufixed48x104',
+ 'ufixed48x112',
+ 'ufixed48x120',
+ 'ufixed48x128',
+ 'ufixed48x136',
+ 'ufixed48x144',
+ 'ufixed48x152',
+ 'ufixed48x160',
+ 'ufixed48x168',
+ 'ufixed48x176',
+ 'ufixed48x184',
+ 'ufixed48x192',
+ 'ufixed48x200',
+ 'ufixed48x208',
+ 'ufixed56x8',
+ 'ufixed56x16',
+ 'ufixed56x24',
+ 'ufixed56x32',
+ 'ufixed56x40',
+ 'ufixed56x48',
+ 'ufixed56x56',
+ 'ufixed56x64',
+ 'ufixed56x72',
+ 'ufixed56x80',
+ 'ufixed56x88',
+ 'ufixed56x96',
+ 'ufixed56x104',
+ 'ufixed56x112',
+ 'ufixed56x120',
+ 'ufixed56x128',
+ 'ufixed56x136',
+ 'ufixed56x144',
+ 'ufixed56x152',
+ 'ufixed56x160',
+ 'ufixed56x168',
+ 'ufixed56x176',
+ 'ufixed56x184',
+ 'ufixed56x192',
+ 'ufixed56x200',
+ 'ufixed64x8',
+ 'ufixed64x16',
+ 'ufixed64x24',
+ 'ufixed64x32',
+ 'ufixed64x40',
+ 'ufixed64x48',
+ 'ufixed64x56',
+ 'ufixed64x64',
+ 'ufixed64x72',
+ 'ufixed64x80',
+ 'ufixed64x88',
+ 'ufixed64x96',
+ 'ufixed64x104',
+ 'ufixed64x112',
+ 'ufixed64x120',
+ 'ufixed64x128',
+ 'ufixed64x136',
+ 'ufixed64x144',
+ 'ufixed64x152',
+ 'ufixed64x160',
+ 'ufixed64x168',
+ 'ufixed64x176',
+ 'ufixed64x184',
+ 'ufixed64x192',
+ 'ufixed72x8',
+ 'ufixed72x16',
+ 'ufixed72x24',
+ 'ufixed72x32',
+ 'ufixed72x40',
+ 'ufixed72x48',
+ 'ufixed72x56',
+ 'ufixed72x64',
+ 'ufixed72x72',
+ 'ufixed72x80',
+ 'ufixed72x88',
+ 'ufixed72x96',
+ 'ufixed72x104',
+ 'ufixed72x112',
+ 'ufixed72x120',
+ 'ufixed72x128',
+ 'ufixed72x136',
+ 'ufixed72x144',
+ 'ufixed72x152',
+ 'ufixed72x160',
+ 'ufixed72x168',
+ 'ufixed72x176',
+ 'ufixed72x184',
+ 'ufixed80x8',
+ 'ufixed80x16',
+ 'ufixed80x24',
+ 'ufixed80x32',
+ 'ufixed80x40',
+ 'ufixed80x48',
+ 'ufixed80x56',
+ 'ufixed80x64',
+ 'ufixed80x72',
+ 'ufixed80x80',
+ 'ufixed80x88',
+ 'ufixed80x96',
+ 'ufixed80x104',
+ 'ufixed80x112',
+ 'ufixed80x120',
+ 'ufixed80x128',
+ 'ufixed80x136',
+ 'ufixed80x144',
+ 'ufixed80x152',
+ 'ufixed80x160',
+ 'ufixed80x168',
+ 'ufixed80x176',
+ 'ufixed88x8',
+ 'ufixed88x16',
+ 'ufixed88x24',
+ 'ufixed88x32',
+ 'ufixed88x40',
+ 'ufixed88x48',
+ 'ufixed88x56',
+ 'ufixed88x64',
+ 'ufixed88x72',
+ 'ufixed88x80',
+ 'ufixed88x88',
+ 'ufixed88x96',
+ 'ufixed88x104',
+ 'ufixed88x112',
+ 'ufixed88x120',
+ 'ufixed88x128',
+ 'ufixed88x136',
+ 'ufixed88x144',
+ 'ufixed88x152',
+ 'ufixed88x160',
+ 'ufixed88x168',
+ 'ufixed96x8',
+ 'ufixed96x16',
+ 'ufixed96x24',
+ 'ufixed96x32',
+ 'ufixed96x40',
+ 'ufixed96x48',
+ 'ufixed96x56',
+ 'ufixed96x64',
+ 'ufixed96x72',
+ 'ufixed96x80',
+ 'ufixed96x88',
+ 'ufixed96x96',
+ 'ufixed96x104',
+ 'ufixed96x112',
+ 'ufixed96x120',
+ 'ufixed96x128',
+ 'ufixed96x136',
+ 'ufixed96x144',
+ 'ufixed96x152',
+ 'ufixed96x160',
+ 'ufixed104x8',
+ 'ufixed104x16',
+ 'ufixed104x24',
+ 'ufixed104x32',
+ 'ufixed104x40',
+ 'ufixed104x48',
+ 'ufixed104x56',
+ 'ufixed104x64',
+ 'ufixed104x72',
+ 'ufixed104x80',
+ 'ufixed104x88',
+ 'ufixed104x96',
+ 'ufixed104x104',
+ 'ufixed104x112',
+ 'ufixed104x120',
+ 'ufixed104x128',
+ 'ufixed104x136',
+ 'ufixed104x144',
+ 'ufixed104x152',
+ 'ufixed112x8',
+ 'ufixed112x16',
+ 'ufixed112x24',
+ 'ufixed112x32',
+ 'ufixed112x40',
+ 'ufixed112x48',
+ 'ufixed112x56',
+ 'ufixed112x64',
+ 'ufixed112x72',
+ 'ufixed112x80',
+ 'ufixed112x88',
+ 'ufixed112x96',
+ 'ufixed112x104',
+ 'ufixed112x112',
+ 'ufixed112x120',
+ 'ufixed112x128',
+ 'ufixed112x136',
+ 'ufixed112x144',
+ 'ufixed120x8',
+ 'ufixed120x16',
+ 'ufixed120x24',
+ 'ufixed120x32',
+ 'ufixed120x40',
+ 'ufixed120x48',
+ 'ufixed120x56',
+ 'ufixed120x64',
+ 'ufixed120x72',
+ 'ufixed120x80',
+ 'ufixed120x88',
+ 'ufixed120x96',
+ 'ufixed120x104',
+ 'ufixed120x112',
+ 'ufixed120x120',
+ 'ufixed120x128',
+ 'ufixed120x136',
+ 'ufixed128x8',
+ 'ufixed128x16',
+ 'ufixed128x24',
+ 'ufixed128x32',
+ 'ufixed128x40',
+ 'ufixed128x48',
+ 'ufixed128x56',
+ 'ufixed128x64',
+ 'ufixed128x72',
+ 'ufixed128x80',
+ 'ufixed128x88',
+ 'ufixed128x96',
+ 'ufixed128x104',
+ 'ufixed128x112',
+ 'ufixed128x120',
+ 'ufixed128x128',
+ 'ufixed136x8',
+ 'ufixed136x16',
+ 'ufixed136x24',
+ 'ufixed136x32',
+ 'ufixed136x40',
+ 'ufixed136x48',
+ 'ufixed136x56',
+ 'ufixed136x64',
+ 'ufixed136x72',
+ 'ufixed136x80',
+ 'ufixed136x88',
+ 'ufixed136x96',
+ 'ufixed136x104',
+ 'ufixed136x112',
+ 'ufixed136x120',
+ 'ufixed144x8',
+ 'ufixed144x16',
+ 'ufixed144x24',
+ 'ufixed144x32',
+ 'ufixed144x40',
+ 'ufixed144x48',
+ 'ufixed144x56',
+ 'ufixed144x64',
+ 'ufixed144x72',
+ 'ufixed144x80',
+ 'ufixed144x88',
+ 'ufixed144x96',
+ 'ufixed144x104',
+ 'ufixed144x112',
+ 'ufixed152x8',
+ 'ufixed152x16',
+ 'ufixed152x24',
+ 'ufixed152x32',
+ 'ufixed152x40',
+ 'ufixed152x48',
+ 'ufixed152x56',
+ 'ufixed152x64',
+ 'ufixed152x72',
+ 'ufixed152x80',
+ 'ufixed152x88',
+ 'ufixed152x96',
+ 'ufixed152x104',
+ 'ufixed160x8',
+ 'ufixed160x16',
+ 'ufixed160x24',
+ 'ufixed160x32',
+ 'ufixed160x40',
+ 'ufixed160x48',
+ 'ufixed160x56',
+ 'ufixed160x64',
+ 'ufixed160x72',
+ 'ufixed160x80',
+ 'ufixed160x88',
+ 'ufixed160x96',
+ 'ufixed168x8',
+ 'ufixed168x16',
+ 'ufixed168x24',
+ 'ufixed168x32',
+ 'ufixed168x40',
+ 'ufixed168x48',
+ 'ufixed168x56',
+ 'ufixed168x64',
+ 'ufixed168x72',
+ 'ufixed168x80',
+ 'ufixed168x88',
+ 'ufixed176x8',
+ 'ufixed176x16',
+ 'ufixed176x24',
+ 'ufixed176x32',
+ 'ufixed176x40',
+ 'ufixed176x48',
+ 'ufixed176x56',
+ 'ufixed176x64',
+ 'ufixed176x72',
+ 'ufixed176x80',
+ 'ufixed184x8',
+ 'ufixed184x16',
+ 'ufixed184x24',
+ 'ufixed184x32',
+ 'ufixed184x40',
+ 'ufixed184x48',
+ 'ufixed184x56',
+ 'ufixed184x64',
+ 'ufixed184x72',
+ 'ufixed192x8',
+ 'ufixed192x16',
+ 'ufixed192x24',
+ 'ufixed192x32',
+ 'ufixed192x40',
+ 'ufixed192x48',
+ 'ufixed192x56',
+ 'ufixed192x64',
+ 'ufixed200x8',
+ 'ufixed200x16',
+ 'ufixed200x24',
+ 'ufixed200x32',
+ 'ufixed200x40',
+ 'ufixed200x48',
+ 'ufixed200x56',
+ 'ufixed208x8',
+ 'ufixed208x16',
+ 'ufixed208x24',
+ 'ufixed208x32',
+ 'ufixed208x40',
+ 'ufixed208x48',
+ 'ufixed216x8',
+ 'ufixed216x16',
+ 'ufixed216x24',
+ 'ufixed216x32',
+ 'ufixed216x40',
+ 'ufixed224x8',
+ 'ufixed224x16',
+ 'ufixed224x24',
+ 'ufixed224x32',
+ 'ufixed232x8',
+ 'ufixed232x16',
+ 'ufixed232x24',
+ 'ufixed240x8',
+ 'ufixed240x16',
+ 'ufixed248x8',
+ 'event',
+ 'emit',
+ 'enum',
+ 'let',
+ 'mapping',
+ 'private',
+ 'public',
+ 'external',
+ 'internal',
+ 'indexed',
+ 'anonymous',
+ 'view',
+ 'pure',
+ 'inherited',
+ 'storage',
+ 'memory',
+ 'virtual',
+ 'calldata',
+ 'override',
+ 'abstract',
+ 'payable',
+ 'nonpayable',
+ 'constants',
+ 'immutable',
+ 'assert',
+ 'require',
+ 'revert',
+ 'blockhash',
+ 'keccak256',
+ 'sha256',
+ 'ripemd160',
+ 'ecrecover',
+ 'addmod',
+ 'mulmod',
+ 'selfdestruct',
+ 'type',
+ 'gasleft',
+ 'abi',
+ 'block',
+ 'bytes',
+ 'msg',
+ 'tx',
+ 'Error',
+ 'Panic',
+ 'exceptions',
+ 'true',
+ 'false',
+ 'var',
+ 'import',
+ 'constant',
+ 'fallback',
+ 'receive',
+ 'delete',
+ 'if',
+ 'else',
+ 'for',
+ 'while',
+ 'do',
+ 'break',
+ 'continue',
+ 'throw',
+ 'returns',
+ 'return',
+ 'suicide',
+ 'new',
+ 'is',
+ 'this',
+ 'super',
+ 'try',
+ 'catch'
+ ],
+
+ operators: [
+ '=',
+ '>',
+ '<',
+ '!',
+ '~',
+ '?',
+ ':',
+ '==',
+ '<=',
+ '>=',
+ '!=',
+ '&&',
+ '||',
+ '++',
+ '--',
+ '+',
+ '-',
+ '*',
+ '/',
+ '&',
+ '|',
+ '^',
+ '%',
+ '<<',
+ '>>',
+ '>>>',
+ '+=',
+ '-=',
+ '*=',
+ '/=',
+ '&=',
+ '|=',
+ '^=',
+ '%=',
+ '<<=',
+ '>>=',
+ '>>>='
+ ],
+
+ // we include these common regular expressions
+ symbols: /[=>](?!@symbols)/, '@brackets'],
+ [
+ /@symbols/,
+ {
+ cases: {
+ '@operators': 'delimiter',
+ '@default': ''
+ }
+ }
+ ],
+
+ // numbers
+ [/\d*\d+[eE]([\-+]?\d+)?(@floatsuffix)/, 'number.float'],
+ [/\d*\.\d+([eE][\-+]?\d+)?(@floatsuffix)/, 'number.float'],
+ [/0[xX][0-9a-fA-F']*[0-9a-fA-F](@integersuffix)/, 'number.hex'],
+ [/0[0-7']*[0-7](@integersuffix)/, 'number.octal'],
+ [/0[bB][0-1']*[0-1](@integersuffix)/, 'number.binary'],
+ [/\d[\d']*\d(@integersuffix)/, 'number'],
+ [/\d(@integersuffix)/, 'number'],
+
+ // delimiter: after number because of .\d floats
+ [/[;,.]/, 'delimiter'],
+
+ // strings
+ [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+ [/"/, 'string', '@string'],
+
+ // characters
+ [/'[^\\']'/, 'string'],
+ [/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
+ [/'/, 'string.invalid']
+ ],
+
+ whitespace: [
+ [/[ \t\r\n]+/, ''],
+ [/\/\*\*(?!\/)/, 'comment.doc', '@doccomment'],
+ [/\/\*/, 'comment', '@comment'],
+ [/\/\/.*$/, 'comment']
+ ],
+
+ comment: [
+ [/[^\/*]+/, 'comment'],
+ [/\*\//, 'comment', '@pop'],
+ [/[\/*]/, 'comment']
+ ],
+ // Identical copy of comment above, except for the addition of .doc
+ doccomment: [
+ [/[^\/*]+/, 'comment.doc'],
+ [/\*\//, 'comment.doc', '@pop'],
+ [/[\/*]/, 'comment.doc']
+ ],
+
+ string: [
+ [/[^\\"]+/, 'string'],
+ [/@escapes/, 'string.escape'],
+ [/\\./, 'string.escape.invalid'],
+ [/"/, 'string', '@pop']
+ ]
+ }
+}
diff --git a/packages/frontend/src/lib/editor/language/syntaxes_solidity.json b/packages/frontend/src/lib/editor/language/syntaxes_solidity.json
new file mode 100644
index 0000000..37c25bd
--- /dev/null
+++ b/packages/frontend/src/lib/editor/language/syntaxes_solidity.json
@@ -0,0 +1,540 @@
+{
+ "fileTypes": [
+ "sol"
+ ],
+ "name": "Solidity",
+ "patterns": [
+ {
+ "include": "#natspec"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#operator"
+ },
+ {
+ "include": "#control"
+ },
+ {
+ "include": "#constant"
+ },
+ {
+ "include": "#number"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#type"
+ },
+ {
+ "include": "#global"
+ },
+ {
+ "include": "#declaration"
+ },
+ {
+ "include": "#function-call"
+ },
+ {
+ "include": "#assembly"
+ },
+ {
+ "include": "#punctuation"
+ }
+ ],
+ "repository": {
+ "natspec": {
+ "patterns": [
+ {
+ "begin": "/\\*\\*",
+ "end": "\\*/",
+ "name": "comment.block.documentation.solidity",
+ "patterns": [
+ {
+ "include": "#natspec-tags"
+ }
+ ]
+ },
+ {
+ "begin": "///",
+ "end": "$",
+ "name": "comment.block.documentation.solidity",
+ "patterns": [
+ {
+ "include": "#natspec-tags"
+ }
+ ]
+ }
+ ]
+ },
+ "natspec-tags": {
+ "patterns": [
+ {
+ "include": "#natspec-tag-title"
+ },
+ {
+ "include": "#natspec-tag-author"
+ },
+ {
+ "include": "#natspec-tag-notice"
+ },
+ {
+ "include": "#natspec-tag-dev"
+ },
+ {
+ "include": "#natspec-tag-param"
+ },
+ {
+ "include": "#natspec-tag-return"
+ }
+ ]
+ },
+ "natspec-tag-title": {
+ "match": "(@title)\\b",
+ "name": "storage.type.title.natspec"
+ },
+ "natspec-tag-author": {
+ "match": "(@author)\\b",
+ "name": "storage.type.author.natspec"
+ },
+ "natspec-tag-notice": {
+ "match": "(@notice)\\b",
+ "name": "storage.type.dev.natspec"
+ },
+ "natspec-tag-dev": {
+ "match": "(@dev)\\b",
+ "name": "storage.type.dev.natspec"
+ },
+ "natspec-tag-param": {
+ "match": "(@param)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.param.natspec"
+ },
+ "3": {
+ "name": "variable.other.natspec"
+ }
+ }
+ },
+ "natspec-tag-return": {
+ "match": "(@return)\\b",
+ "name": "storage.type.return.natspec"
+ },
+ "comment": {
+ "patterns": [
+ {
+ "include": "#comment-line"
+ },
+ {
+ "include": "#comment-block"
+ }
+ ]
+ },
+ "comment-line": {
+ "match": "(?(?!>)|>=|\\&\\&|\\|\\||\\:(?!=)|\\?)",
+ "name": "keyword.operator.logic.solidity"
+ },
+ "operator-mapping": {
+ "match": "(=>)",
+ "name": "keyword.operator.mapping.solidity"
+ },
+ "operator-arithmetic": {
+ "match": "(\\+|\\-|\\/|\\*)",
+ "name": "keyword.operator.arithmetic.solidity"
+ },
+ "operator-binary": {
+ "match": "(\\^|\\&|\\||<<|>>)",
+ "name": "keyword.operator.binary.solidity"
+ },
+ "operator-assignment": {
+ "match": "(\\:?=)",
+ "name": "keyword.operator.assignment.solidity"
+ },
+ "control": {
+ "patterns": [
+ {
+ "include": "#control-flow"
+ },
+ {
+ "include": "#control-using"
+ },
+ {
+ "include": "#control-import"
+ },
+ {
+ "include": "#control-pragma"
+ },
+ {
+ "include": "#control-underscore"
+ },
+ {
+ "include": "#control-other"
+ }
+ ]
+ },
+ "control-flow": {
+ "match": "\\b(if|else|for|while|do|break|continue|throw|returns?)\\b",
+ "name": "keyword.control.flow.solidity"
+ },
+ "control-using": {
+ "match": "\\b(using)\\b",
+ "name": "keyword.control.using.solidity"
+ },
+ "control-import": {
+ "match": "\\b(import)\\b",
+ "name": "keyword.control.import.solidity"
+ },
+ "control-pragma": {
+ "match": "\\b(pragma)(?:\\s+([A-Za-z_]\\w+)\\s+([^\\s]+))?\\b",
+ "captures": {
+ "1": {
+ "name": "keyword.control.pragma.solidity"
+ },
+ "2": {
+ "name": "entity.name.tag.pragma.solidity"
+ },
+ "3": {
+ "name": "constant.other.pragma.solidity"
+ }
+ }
+ },
+ "control-underscore": {
+ "match": "\\b(_)\\b",
+ "name": "constant.other.underscore.solidity"
+ },
+ "control-other": {
+ "match": "\\b(new|delete|emit)\\b",
+ "name": "keyword.control.solidity"
+ },
+ "constant": {
+ "patterns": [
+ {
+ "include": "#constant-boolean"
+ },
+ {
+ "include": "#constant-time"
+ },
+ {
+ "include": "#constant-currency"
+ }
+ ]
+ },
+ "constant-boolean": {
+ "match": "\\b(true|false)\\b",
+ "name": "constant.language.boolean.solidity"
+ },
+ "constant-time": {
+ "match": "\\b(seconds|minutes|hours|days|weeks|years)\\b",
+ "name": "constant.language.time.solidity"
+ },
+ "constant-currency": {
+ "match": "\\b(ether|wei)\\b",
+ "name": "constant.language.currency.solidity"
+ },
+ "number": {
+ "patterns": [
+ {
+ "include": "#number-decimal"
+ },
+ {
+ "include": "#number-hex"
+ }
+ ]
+ },
+ "number-decimal": {
+ "match": "\\b(\\d+(\\.\\d+)?)\\b",
+ "name": "constant.numeric.decimal.solidity"
+ },
+ "number-hex": {
+ "match": "\\b(0[xX][a-fA-F0-9]+)\\b",
+ "name": "constant.numeric.hexadecimal.solidity"
+ },
+ "string": {
+ "patterns": [
+ {
+ "match": "\\\".*?\\\"",
+ "name": "string.quoted.double.solidity"
+ },
+ {
+ "match": "\\'.*?\\'",
+ "name": "string.quoted.single.solidity"
+ }
+ ]
+ },
+ "type": {
+ "patterns": [
+ {
+ "include": "#type-primitive"
+ }
+ ]
+ },
+ "type-primitive": {
+ "match": "\\b(address|string\\d*|bytes\\d*|int\\d*|uint\\d*|bool|hash\\d*)\\b",
+ "name": "support.type.primitive.solidity"
+ },
+ "global": {
+ "patterns": [
+ {
+ "include": "#global-variables"
+ },
+ {
+ "include": "#global-functions"
+ }
+ ]
+ },
+ "global-variables": {
+ "patterns": [
+ {
+ "match": "\\b(msg|block|tx|now)\\b",
+ "name": "variable.language.transaction.solidity"
+ },
+ {
+ "match": "\\b(this)\\b",
+ "name": "variable.language.this.solidity"
+ },
+ {
+ "match": "\\b(super)\\b",
+ "name": "variable.language.super.solidity"
+ }
+ ]
+ },
+ "global-functions": {
+ "patterns": [
+ {
+ "match": "\\b(require|assert|revert)\\b",
+ "name": "keyword.control.exceptions.solidity"
+ },
+ {
+ "match": "\\b(selfdestruct|suicide)\\b",
+ "name": "keyword.control.contract.solidity"
+ },
+ {
+ "match": "\\b(addmod|mulmod|keccak256|sha256|sha3|ripemd160|ecrecover)\\b",
+ "name": "support.function.math.solidity"
+ },
+ {
+ "match": "\\b(blockhash|gasleft)\\b",
+ "name": "variable.language.transaction.solidity"
+ }
+ ]
+ },
+ "declaration": {
+ "patterns": [
+ {
+ "include": "#declaration-contract"
+ },
+ {
+ "include": "#declaration-interface"
+ },
+ {
+ "include": "#declaration-library"
+ },
+ {
+ "include": "#declaration-struct"
+ },
+ {
+ "include": "#declaration-event"
+ },
+ {
+ "include": "#declaration-enum"
+ },
+ {
+ "include": "#declaration-function"
+ },
+ {
+ "include": "#declaration-constructor"
+ },
+ {
+ "include": "#declaration-modifier"
+ },
+ {
+ "include": "#declaration-mapping"
+ }
+ ]
+ },
+ "declaration-contract": {
+ "patterns": [
+ {
+ "match": "\\b(contract)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.contract.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.contract.solidity"
+ }
+ }
+ },
+ {
+ "match": "\\b(is)\\b",
+ "name": "storage.modifier.is.solidity"
+ }
+ ]
+ },
+ "declaration-interface": {
+ "match": "\\b(interface)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.interface.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.interface.solidity"
+ }
+ }
+ },
+ "declaration-library": {
+ "match": "\\b(library)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.library.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.library.solidity"
+ }
+ }
+ },
+ "declaration-struct": {
+ "match": "\\b(struct)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.struct.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.struct.solidity"
+ }
+ }
+ },
+ "declaration-event": {
+ "match": "\\b(event)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.event.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.event.solidity"
+ }
+ }
+ },
+ "declaration-constructor": {
+ "match": "\\b(constructor)\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.constructor.solidity"
+ }
+ }
+ },
+ "declaration-enum": {
+ "match": "\\b(enum)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.enum.solidity"
+ },
+ "3": {
+ "name": "entity.name.type.enum.solidity"
+ }
+ }
+ },
+ "declaration-function": {
+ "patterns": [
+ {
+ "match": "\\b(function)\\s+([A-Za-z_]\\w*)\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.function.solidity"
+ },
+ "2": {
+ "name": "entity.name.function.solidity"
+ }
+ }
+ },
+ {
+ "match": "\\b(private|public|internal|external|constant|pure|view|payable|nonpayable|inherited|indexed|storage|memory|virtual|override)\\b",
+ "name": "storage.type.mofifier.solidity"
+ }
+ ]
+ },
+ "declaration-modifier": {
+ "match": "\\b(modifier)(\\s+([A-Za-z_]\\w*))?\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type.modifier.solidity"
+ },
+ "3": {
+ "name": "entity.name.function.solidity"
+ }
+ }
+ },
+ "declaration-mapping": {
+ "match": "\\b(mapping)\\b",
+ "name": "storage.type.mapping.solidity"
+ },
+ "function-call": {
+ "match": "\\b([A-Za-z_]\\w*)\\s*\\(",
+ "captures": {
+ "1": {
+ "name": "entity.name.function.solidity"
+ }
+ }
+ },
+ "assembly": {
+ "patterns": [
+ {
+ "match": "\\b(assembly)\\b",
+ "name": "keyword.control.assembly.solidity"
+ },
+ {
+ "match": "\\b(let)\\b",
+ "name": "storage.type.assembly.solidity"
+ }
+ ]
+ },
+ "punctuation": {
+ "patterns": [
+ {
+ "match": ";",
+ "name": "punctuation.terminator.statement.solidity"
+ },
+ {
+ "match": "\\.",
+ "name": "punctuation.accessor.solidity"
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.solidity"
+ }
+ ]
+ }
+ },
+ "scopeName": "source.solidity",
+ "uuid": "123"
+}
\ No newline at end of file
diff --git a/packages/frontend/src/lib/editor/server.ts b/packages/frontend/src/lib/editor/server.ts
new file mode 100644
index 0000000..9d43a41
--- /dev/null
+++ b/packages/frontend/src/lib/editor/server.ts
@@ -0,0 +1,31 @@
+import init, { InitOutput, serve, ServerConfig } from "@/lib/wasm/demo_lsp_browser";
+import { FromServer, IntoServer } from "./codec";
+
+let server: null | Server;
+
+export default class Server {
+ readonly initOutput: InitOutput;
+ readonly #intoServer: IntoServer;
+ readonly #fromServer: FromServer;
+
+ private constructor(initOutput: InitOutput, intoServer: IntoServer, fromServer: FromServer) {
+ this.initOutput = initOutput;
+ this.#intoServer = intoServer;
+ this.#fromServer = fromServer;
+ }
+
+ static async initialize(intoServer: IntoServer, fromServer: FromServer): Promise {
+ if (null == server) {
+ const initOutput = await init();
+ server = new Server(initOutput, intoServer, fromServer);
+ } else {
+ console.warn("Server already initialized; ignoring");
+ }
+ return server;
+ }
+
+ async start(): Promise {
+ const config = new ServerConfig(this.#intoServer, this.#fromServer);
+ await serve(config);
+ }
+}
diff --git a/packages/frontend/src/lib/editor/services.ts b/packages/frontend/src/lib/editor/services.ts
new file mode 100644
index 0000000..1045ef8
--- /dev/null
+++ b/packages/frontend/src/lib/editor/services.ts
@@ -0,0 +1,47 @@
+import { Monaco } from "@monaco-editor/react";
+import Client from "./client";
+import {
+ DidChangeTextDocumentNotification,
+ DidChangeTextDocumentParams,
+ DidOpenTextDocumentNotification,
+ DidOpenTextDocumentParams,
+} from "vscode-languageserver-protocol";
+import { editor } from "monaco-editor-core";
+import Language from "./language";
+import { monacoToProtocol } from "./utils";
+
+export class EditorService {
+ constructor(private client: Client) {}
+
+ public fileOpened(model: editor.ITextModel): void {
+ const params: DidOpenTextDocumentParams = {
+ textDocument: {
+ uri: model.uri.toString(),
+ languageId: model.getLanguageId(),
+ version: 0,
+ text: model.getValue(),
+ },
+ };
+
+ this.client.notify(DidOpenTextDocumentNotification.type.method, params);
+ }
+
+ public fileChanged(model: editor.ITextModel): void {
+ const content = model.getValue();
+
+ const params: DidChangeTextDocumentParams = {
+ textDocument: {
+ uri: model.uri.toString(),
+ version: 0,
+ },
+ contentChanges: [
+ {
+ range: monacoToProtocol.asRange(model.getFullModelRange()),
+ text: content!,
+ },
+ ],
+ };
+
+ this.client.notify(DidChangeTextDocumentNotification.type.method, params);
+ }
+}
diff --git a/packages/frontend/src/lib/editor/tracer.ts b/packages/frontend/src/lib/editor/tracer.ts
new file mode 100644
index 0000000..7ee236e
--- /dev/null
+++ b/packages/frontend/src/lib/editor/tracer.ts
@@ -0,0 +1,17 @@
+import * as proto from "vscode-languageserver-protocol";
+
+// const clientChannel = document.getElementById("channel-client") as HTMLTextAreaElement;
+// const serverChannel = document.getElementById("channel-server") as HTMLTextAreaElement;
+
+export default class Tracer {
+ static client(message: string): void {
+ // clientChannel.value += message;
+ // clientChannel.value += "\n";
+ }
+
+ static server(input: string | proto.Message): void {
+ const message: string = typeof input === "string" ? input : JSON.stringify(input);
+ // serverChannel.value += message;
+ // serverChannel.value += "\n";
+ }
+}
diff --git a/packages/frontend/src/lib/editor/utils.ts b/packages/frontend/src/lib/editor/utils.ts
new file mode 100644
index 0000000..cc187fd
--- /dev/null
+++ b/packages/frontend/src/lib/editor/utils.ts
@@ -0,0 +1,5 @@
+import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from "./converter";
+import * as monaco from "monaco-editor";
+
+export const protocolToMonaco = new ProtocolToMonacoConverter(monaco);
+export const monacoToProtocol = new MonacoToProtocolConverter(monaco);
diff --git a/packages/frontend/src/lib/googlebackup.ts b/packages/frontend/src/lib/googlebackup.ts
new file mode 100644
index 0000000..f31ebcf
--- /dev/null
+++ b/packages/frontend/src/lib/googlebackup.ts
@@ -0,0 +1,97 @@
+import { logger } from "@/state/utils";
+import { ExpNodeType, FolderType } from "@/types/explorer";
+
+async function createGoogleFolder(accessToken: string, folderName: string, parentFolderId?: string): Promise {
+ // Create a folder in Google Drive
+ const response = await fetch("https://www.googleapis.com/drive/v3/files", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ name: folderName,
+ mimeType: "application/vnd.google-apps.folder",
+ parents: parentFolderId ? [parentFolderId] : [],
+ }),
+ });
+
+ console.log(response);
+
+ if (!response.ok) {
+ logger.error(`Failed to Create FOLDER: ${folderName}`);
+ throw new Error("Failed to create folder");
+ }
+
+ const data = await response.json();
+
+ logger.info(`Successfully Created FOLDER: ${folderName}`);
+ return data.id as string;
+}
+
+async function createGoogleFile({
+ content,
+ folderId,
+ name,
+ accessToken,
+}: {
+ folderId: string;
+ name: string;
+ content: string;
+ accessToken: string;
+}) {
+ const file = new Blob([content], { type: "text/plain" });
+ const metadata = {
+ name,
+ mimeType: "text/plain",
+ parents: [folderId],
+ };
+
+ const formData = new FormData();
+
+ formData.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
+ formData.append("file", file);
+
+ const response = await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ body: formData,
+ });
+
+ if (!response.ok) {
+ logger.error(`Failed to Create FILE: ${name}`);
+ return false;
+ }
+ const data = await response.json();
+ logger.info(`Successfully Created FILE: ${name}`);
+
+ return true;
+}
+
+async function createGoogleBackup(
+ folder: FolderType,
+ accessToken: string,
+ files: Record,
+ parentFolderId?: string,
+) {
+ const folderId = await createGoogleFolder(accessToken, folder.name, parentFolderId);
+
+ for (const key in folder.items) {
+ const item = folder.items[key];
+
+ if (item.type === ExpNodeType.FOLDER) {
+ await createGoogleBackup(item, accessToken, files, folderId);
+ } else {
+ await createGoogleFile({
+ content: files[item.path],
+ folderId,
+ name: item.name,
+ accessToken,
+ });
+ }
+ }
+}
+
+export default createGoogleBackup;
diff --git a/packages/frontend/src/lib/theme.ts b/packages/frontend/src/lib/theme.ts
new file mode 100644
index 0000000..2275fb1
--- /dev/null
+++ b/packages/frontend/src/lib/theme.ts
@@ -0,0 +1,14 @@
+export function getSystemTheme(): "dark" | "light" {
+ // Check if the `window` and `matchMedia` APIs are available
+ if (typeof window !== "undefined" && window.matchMedia) {
+ const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ return darkModeQuery.matches ? "dark" : "light";
+ }
+
+ // Default fallback if the system theme can't be determined
+ return "light";
+}
+
+export function isDarkTheme(): boolean {
+ return getSystemTheme() === "dark";
+}
diff --git a/packages/frontend/src/lib/utils.ts b/packages/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..f5c253a
--- /dev/null
+++ b/packages/frontend/src/lib/utils.ts
@@ -0,0 +1,46 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
+
+export const downloadBlob = (code: number[]): void => {
+ const blob = new Blob([new Uint8Array(code).buffer]);
+
+ const a = document.createElement("a");
+ a.download = "result.contract";
+ a.href = URL.createObjectURL(blob);
+ a.dataset.downloadurl = ["application/json", a.download, a.href].join(":");
+ a.style.display = "none";
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ setTimeout(() => {
+ URL.revokeObjectURL(a.href);
+ }, 1500);
+};
+
+/**
+ * Generates a random ID.
+ * @param length - The desired length of the ID. Default is 10.
+ * @returns A randomly generated ID as a string.
+ */
+export function generateRandomId(length: number = 10): string {
+ const characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let result: string = '';
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
+ }
+ return result;
+}
+
+
+export function onEnter(callback: () => void) {
+ return (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ callback();
+ }
+ };
+}
diff --git a/packages/frontend/src/lib/wasm/demo_lsp_browser.d.ts b/packages/frontend/src/lib/wasm/demo_lsp_browser.d.ts
new file mode 100644
index 0000000..1af3a41
--- /dev/null
+++ b/packages/frontend/src/lib/wasm/demo_lsp_browser.d.ts
@@ -0,0 +1,88 @@
+/* tslint:disable */
+/* eslint-disable */
+export function serve(config: ServerConfig): Promise;
+/**
+ * The `ReadableStreamType` enum.
+ *
+ * *This API requires the following crate features to be activated: `ReadableStreamType`*
+ */
+type ReadableStreamType = "bytes";
+export class IntoUnderlyingByteSource {
+ private constructor();
+ free(): void;
+ start(controller: ReadableByteStreamController): void;
+ pull(controller: ReadableByteStreamController): Promise;
+ cancel(): void;
+ readonly type: ReadableStreamType;
+ readonly autoAllocateChunkSize: number;
+}
+export class IntoUnderlyingSink {
+ private constructor();
+ free(): void;
+ write(chunk: any): Promise;
+ close(): Promise;
+ abort(reason: any): Promise;
+}
+export class IntoUnderlyingSource {
+ private constructor();
+ free(): void;
+ pull(controller: ReadableStreamDefaultController): Promise;
+ cancel(): void;
+}
+export class ServerConfig {
+ free(): void;
+ constructor(into_server: AsyncIterator, from_server: WritableStream);
+}
+
+export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
+
+export interface InitOutput {
+ readonly memory: WebAssembly.Memory;
+ readonly __wbg_serverconfig_free: (a: number, b: number) => void;
+ readonly serverconfig_new: (a: any, b: any) => number;
+ readonly serve: (a: number) => any;
+ readonly __wbg_intounderlyingbytesource_free: (a: number, b: number) => void;
+ readonly intounderlyingbytesource_type: (a: number) => number;
+ readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number;
+ readonly intounderlyingbytesource_start: (a: number, b: any) => void;
+ readonly intounderlyingbytesource_pull: (a: number, b: any) => any;
+ readonly intounderlyingbytesource_cancel: (a: number) => void;
+ readonly __wbg_intounderlyingsource_free: (a: number, b: number) => void;
+ readonly intounderlyingsource_pull: (a: number, b: any) => any;
+ readonly intounderlyingsource_cancel: (a: number) => void;
+ readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void;
+ readonly intounderlyingsink_write: (a: number, b: any) => any;
+ readonly intounderlyingsink_close: (a: number) => any;
+ readonly intounderlyingsink_abort: (a: number, b: any) => any;
+ readonly __externref_table_alloc: () => number;
+ readonly __wbindgen_export_1: WebAssembly.Table;
+ readonly __wbindgen_exn_store: (a: number) => void;
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
+ readonly __wbindgen_malloc: (a: number, b: number) => number;
+ readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
+ readonly __wbindgen_export_6: WebAssembly.Table;
+ readonly closure731_externref_shim: (a: number, b: number, c: any) => void;
+ readonly closure1257_externref_shim: (a: number, b: number, c: any, d: any) => void;
+ readonly __wbindgen_start: () => void;
+}
+
+export type SyncInitInput = BufferSource | WebAssembly.Module;
+/**
+* Instantiates the given `module`, which can either be bytes or
+* a precompiled `WebAssembly.Module`.
+*
+* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
+*
+* @returns {InitOutput}
+*/
+export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
+
+/**
+* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
+* for everything else, calls `WebAssembly.instantiate` directly.
+*
+* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated.
+*
+* @returns {Promise}
+*/
+export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise;
diff --git a/packages/frontend/src/lib/wasm/demo_lsp_browser.js b/packages/frontend/src/lib/wasm/demo_lsp_browser.js
new file mode 100644
index 0000000..dc55111
--- /dev/null
+++ b/packages/frontend/src/lib/wasm/demo_lsp_browser.js
@@ -0,0 +1,742 @@
+let wasm;
+
+function isLikeNone(x) {
+ return x === undefined || x === null;
+}
+
+function addToExternrefTable0(obj) {
+ const idx = wasm.__externref_table_alloc();
+ wasm.__wbindgen_export_1.set(idx, obj);
+ return idx;
+}
+
+function handleError(f, args) {
+ try {
+ return f.apply(this, args);
+ } catch (e) {
+ console.log(e);
+ const idx = addToExternrefTable0(e);
+ wasm.__wbindgen_exn_store(idx);
+ }
+}
+
+const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
+
+let cachedUint8ArrayMemory0 = null;
+
+function getUint8ArrayMemory0() {
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachedUint8ArrayMemory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+ ptr = ptr >>> 0;
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
+}
+
+let WASM_VECTOR_LEN = 0;
+
+const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+ ? function (arg, view) {
+ return cachedTextEncoder.encodeInto(arg, view);
+}
+ : function (arg, view) {
+ const buf = cachedTextEncoder.encode(arg);
+ view.set(buf);
+ return {
+ read: arg.length,
+ written: buf.length
+ };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+ if (realloc === undefined) {
+ const buf = cachedTextEncoder.encode(arg);
+ const ptr = malloc(buf.length, 1) >>> 0;
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
+ WASM_VECTOR_LEN = buf.length;
+ return ptr;
+ }
+
+ let len = arg.length;
+ let ptr = malloc(len, 1) >>> 0;
+
+ const mem = getUint8ArrayMemory0();
+
+ let offset = 0;
+
+ for (; offset < len; offset++) {
+ const code = arg.charCodeAt(offset);
+ if (code > 0x7F) break;
+ mem[ptr + offset] = code;
+ }
+
+ if (offset !== len) {
+ if (offset !== 0) {
+ arg = arg.slice(offset);
+ }
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
+ const ret = encodeString(arg, view);
+
+ offset += ret.written;
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
+ }
+
+ WASM_VECTOR_LEN = offset;
+ return ptr;
+}
+
+let cachedDataViewMemory0 = null;
+
+function getDataViewMemory0() {
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
+ }
+ return cachedDataViewMemory0;
+}
+
+const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(state => {
+ wasm.__wbindgen_export_6.get(state.dtor)(state.a, state.b)
+});
+
+function makeMutClosure(arg0, arg1, dtor, f) {
+ const state = { a: arg0, b: arg1, cnt: 1, dtor };
+ const real = (...args) => {
+ // First up with a closure we increment the internal reference
+ // count. This ensures that the Rust closure environment won't
+ // be deallocated while we're invoking it.
+ state.cnt++;
+ const a = state.a;
+ state.a = 0;
+ try {
+ return f(a, state.b, ...args);
+ } finally {
+ if (--state.cnt === 0) {
+ wasm.__wbindgen_export_6.get(state.dtor)(a, state.b);
+ CLOSURE_DTORS.unregister(state);
+ } else {
+ state.a = a;
+ }
+ }
+ };
+ real.original = state;
+ CLOSURE_DTORS.register(real, state, state);
+ return real;
+}
+
+function debugString(val) {
+ // primitive types
+ const type = typeof val;
+ if (type == 'number' || type == 'boolean' || val == null) {
+ return `${val}`;
+ }
+ if (type == 'string') {
+ return `"${val}"`;
+ }
+ if (type == 'symbol') {
+ const description = val.description;
+ if (description == null) {
+ return 'Symbol';
+ } else {
+ return `Symbol(${description})`;
+ }
+ }
+ if (type == 'function') {
+ const name = val.name;
+ if (typeof name == 'string' && name.length > 0) {
+ return `Function(${name})`;
+ } else {
+ return 'Function';
+ }
+ }
+ // objects
+ if (Array.isArray(val)) {
+ const length = val.length;
+ let debug = '[';
+ if (length > 0) {
+ debug += debugString(val[0]);
+ }
+ for(let i = 1; i < length; i++) {
+ debug += ', ' + debugString(val[i]);
+ }
+ debug += ']';
+ return debug;
+ }
+ // Test for built-in
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
+ let className;
+ if (builtInMatches && builtInMatches.length > 1) {
+ className = builtInMatches[1];
+ } else {
+ // Failed to match the standard '[object ClassName]'
+ return toString.call(val);
+ }
+ if (className == 'Object') {
+ // we're a user defined class or Object
+ // JSON.stringify avoids problems with cycles, and is generally much
+ // easier than looping through ownProperties of `val`.
+ try {
+ return 'Object(' + JSON.stringify(val) + ')';
+ } catch (_) {
+ return 'Object';
+ }
+ }
+ // errors
+ if (val instanceof Error) {
+ return `${val.name}: ${val.message}\n${val.stack}`;
+ }
+ // TODO we could test for more things here, like `Set`s and `Map`s.
+ return className;
+}
+
+function _assertClass(instance, klass) {
+ if (!(instance instanceof klass)) {
+ throw new Error(`expected instance of ${klass.name}`);
+ }
+}
+/**
+ * @param {ServerConfig} config
+ * @returns {Promise}
+ */
+export function serve(config) {
+ _assertClass(config, ServerConfig);
+ var ptr0 = config.__destroy_into_raw();
+ const ret = wasm.serve(ptr0);
+ return ret;
+}
+
+function __wbg_adapter_26(arg0, arg1, arg2) {
+ wasm.closure731_externref_shim(arg0, arg1, arg2);
+}
+
+function __wbg_adapter_91(arg0, arg1, arg2, arg3) {
+ wasm.closure1257_externref_shim(arg0, arg1, arg2, arg3);
+}
+
+const __wbindgen_enum_ReadableStreamType = ["bytes"];
+
+const IntoUnderlyingByteSourceFinalization = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingbytesource_free(ptr >>> 0, 1));
+
+export class IntoUnderlyingByteSource {
+
+ __destroy_into_raw() {
+ const ptr = this.__wbg_ptr;
+ this.__wbg_ptr = 0;
+ IntoUnderlyingByteSourceFinalization.unregister(this);
+ return ptr;
+ }
+
+ free() {
+ const ptr = this.__destroy_into_raw();
+ wasm.__wbg_intounderlyingbytesource_free(ptr, 0);
+ }
+ /**
+ * @returns {ReadableStreamType}
+ */
+ get type() {
+ const ret = wasm.intounderlyingbytesource_type(this.__wbg_ptr);
+ return __wbindgen_enum_ReadableStreamType[ret];
+ }
+ /**
+ * @returns {number}
+ */
+ get autoAllocateChunkSize() {
+ const ret = wasm.intounderlyingbytesource_autoAllocateChunkSize(this.__wbg_ptr);
+ return ret >>> 0;
+ }
+ /**
+ * @param {ReadableByteStreamController} controller
+ */
+ start(controller) {
+ wasm.intounderlyingbytesource_start(this.__wbg_ptr, controller);
+ }
+ /**
+ * @param {ReadableByteStreamController} controller
+ * @returns {Promise}
+ */
+ pull(controller) {
+ const ret = wasm.intounderlyingbytesource_pull(this.__wbg_ptr, controller);
+ return ret;
+ }
+ cancel() {
+ const ptr = this.__destroy_into_raw();
+ wasm.intounderlyingbytesource_cancel(ptr);
+ }
+}
+
+const IntoUnderlyingSinkFinalization = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingsink_free(ptr >>> 0, 1));
+
+export class IntoUnderlyingSink {
+
+ __destroy_into_raw() {
+ const ptr = this.__wbg_ptr;
+ this.__wbg_ptr = 0;
+ IntoUnderlyingSinkFinalization.unregister(this);
+ return ptr;
+ }
+
+ free() {
+ const ptr = this.__destroy_into_raw();
+ wasm.__wbg_intounderlyingsink_free(ptr, 0);
+ }
+ /**
+ * @param {any} chunk
+ * @returns {Promise}
+ */
+ write(chunk) {
+ const ret = wasm.intounderlyingsink_write(this.__wbg_ptr, chunk);
+ return ret;
+ }
+ /**
+ * @returns {Promise}
+ */
+ close() {
+ const ptr = this.__destroy_into_raw();
+ const ret = wasm.intounderlyingsink_close(ptr);
+ return ret;
+ }
+ /**
+ * @param {any} reason
+ * @returns {Promise}
+ */
+ abort(reason) {
+ const ptr = this.__destroy_into_raw();
+ const ret = wasm.intounderlyingsink_abort(ptr, reason);
+ return ret;
+ }
+}
+
+const IntoUnderlyingSourceFinalization = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingsource_free(ptr >>> 0, 1));
+
+export class IntoUnderlyingSource {
+
+ __destroy_into_raw() {
+ const ptr = this.__wbg_ptr;
+ this.__wbg_ptr = 0;
+ IntoUnderlyingSourceFinalization.unregister(this);
+ return ptr;
+ }
+
+ free() {
+ const ptr = this.__destroy_into_raw();
+ wasm.__wbg_intounderlyingsource_free(ptr, 0);
+ }
+ /**
+ * @param {ReadableStreamDefaultController} controller
+ * @returns {Promise}
+ */
+ pull(controller) {
+ const ret = wasm.intounderlyingsource_pull(this.__wbg_ptr, controller);
+ return ret;
+ }
+ cancel() {
+ const ptr = this.__destroy_into_raw();
+ wasm.intounderlyingsource_cancel(ptr);
+ }
+}
+
+const ServerConfigFinalization = (typeof FinalizationRegistry === 'undefined')
+ ? { register: () => {}, unregister: () => {} }
+ : new FinalizationRegistry(ptr => wasm.__wbg_serverconfig_free(ptr >>> 0, 1));
+
+export class ServerConfig {
+
+ __destroy_into_raw() {
+ const ptr = this.__wbg_ptr;
+ this.__wbg_ptr = 0;
+ ServerConfigFinalization.unregister(this);
+ return ptr;
+ }
+
+ free() {
+ const ptr = this.__destroy_into_raw();
+ wasm.__wbg_serverconfig_free(ptr, 0);
+ }
+ /**
+ * @param {AsyncIterator} into_server
+ * @param {WritableStream} from_server
+ */
+ constructor(into_server, from_server) {
+ const ret = wasm.serverconfig_new(into_server, from_server);
+ this.__wbg_ptr = ret >>> 0;
+ ServerConfigFinalization.register(this, this.__wbg_ptr, this);
+ return this;
+ }
+}
+
+async function __wbg_load(module, imports) {
+ if (typeof Response === 'function' && module instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
+ try {
+ return await WebAssembly.instantiateStreaming(module, imports);
+
+ } catch (e) {
+ if (module.headers.get('Content-Type') != 'application/wasm') {
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ const bytes = await module.arrayBuffer();
+ return await WebAssembly.instantiate(bytes, imports);
+
+ } else {
+ const instance = await WebAssembly.instantiate(module, imports);
+
+ if (instance instanceof WebAssembly.Instance) {
+ return { instance, module };
+
+ } else {
+ return instance;
+ }
+ }
+}
+
+function __wbg_get_imports() {
+ const imports = {};
+ imports.wbg = {};
+ imports.wbg.__wbg_buffer_61b7ce01341d7f88 = function(arg0) {
+ const ret = arg0.buffer;
+ return ret;
+ };
+ imports.wbg.__wbg_buffer_dc5dbfa8d5fb28cf = function(arg0) {
+ const ret = arg0.buffer;
+ return ret;
+ };
+ imports.wbg.__wbg_byobRequest_1fc36a0c1e98611b = function(arg0) {
+ const ret = arg0.byobRequest;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_byteLength_1b2d953758afc500 = function(arg0) {
+ const ret = arg0.byteLength;
+ return ret;
+ };
+ imports.wbg.__wbg_byteOffset_7ef484c6c1d473e9 = function(arg0) {
+ const ret = arg0.byteOffset;
+ return ret;
+ };
+ imports.wbg.__wbg_call_500db948e69c7330 = function() { return handleError(function (arg0, arg1, arg2) {
+ const ret = arg0.call(arg1, arg2);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_call_b0d8e36992d9900d = function() { return handleError(function (arg0, arg1) {
+ const ret = arg0.call(arg1);
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_close_59511bda900d85a8 = function() { return handleError(function (arg0) {
+ arg0.close();
+ }, arguments) };
+ imports.wbg.__wbg_close_65cb23eb0316f916 = function() { return handleError(function (arg0) {
+ arg0.close();
+ }, arguments) };
+ imports.wbg.__wbg_close_7b3c2334f3731ec4 = function(arg0) {
+ const ret = arg0.close();
+ return ret;
+ };
+ imports.wbg.__wbg_done_f22c1561fa919baa = function(arg0) {
+ const ret = arg0.done;
+ return ret;
+ };
+ imports.wbg.__wbg_enqueue_3997a55771b5212a = function() { return handleError(function (arg0, arg1) {
+ arg0.enqueue(arg1);
+ }, arguments) };
+ imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
+ let deferred0_0;
+ let deferred0_1;
+ try {
+ deferred0_0 = arg0;
+ deferred0_1 = arg1;
+ console.error(getStringFromWasm0(arg0, arg1));
+ } finally {
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
+ }
+ };
+ imports.wbg.__wbg_getWriter_dd1c7a1972bcd348 = function() { return handleError(function (arg0) {
+ const ret = arg0.getWriter();
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_instanceof_Uint8Array_28af5bc19d6acad8 = function(arg0) {
+ let result;
+ try {
+ result = arg0 instanceof Uint8Array;
+ } catch (_) {
+ result = false;
+ }
+ const ret = result;
+ return ret;
+ };
+ imports.wbg.__wbg_length_65d1cd11729ced11 = function(arg0) {
+ const ret = arg0.length;
+ return ret;
+ };
+ imports.wbg.__wbg_log_464d1b2190ca1e04 = function(arg0) {
+ console.log(arg0);
+ };
+ imports.wbg.__wbg_new_3d446df9155128ef = function(arg0, arg1) {
+ try {
+ var state0 = {a: arg0, b: arg1};
+ var cb0 = (arg0, arg1) => {
+ const a = state0.a;
+ state0.a = 0;
+ try {
+ return __wbg_adapter_91(a, state0.b, arg0, arg1);
+ } finally {
+ state0.a = a;
+ }
+ };
+ const ret = new Promise(cb0);
+ return ret;
+ } finally {
+ state0.a = state0.b = 0;
+ }
+ };
+ imports.wbg.__wbg_new_3ff5b33b1ce712df = function(arg0) {
+ const ret = new Uint8Array(arg0);
+ return ret;
+ };
+ imports.wbg.__wbg_new_6799ef630abee97c = function(arg0, arg1) {
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
+ return ret;
+ };
+ imports.wbg.__wbg_new_8a6f238a6ece86ea = function() {
+ const ret = new Error();
+ return ret;
+ };
+ imports.wbg.__wbg_newnoargs_fd9e4bf8be2bc16d = function(arg0, arg1) {
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
+ return ret;
+ };
+ imports.wbg.__wbg_newwithbyteoffsetandlength_ba35896968751d91 = function(arg0, arg1, arg2) {
+ const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);
+ return ret;
+ };
+ imports.wbg.__wbg_next_97adbc0d5bb171e7 = function() { return handleError(function (arg0) {
+ const ret = arg0.next();
+ return ret;
+ }, arguments) };
+ imports.wbg.__wbg_queueMicrotask_2181040e064c0dc8 = function(arg0) {
+ queueMicrotask(arg0);
+ };
+ imports.wbg.__wbg_queueMicrotask_ef9ac43769cbcc4f = function(arg0) {
+ const ret = arg0.queueMicrotask;
+ return ret;
+ };
+ imports.wbg.__wbg_ready_26e7f2af8ced9ce5 = function(arg0) {
+ const ret = arg0.ready;
+ return ret;
+ };
+ imports.wbg.__wbg_releaseLock_7878dddc005f738f = function(arg0) {
+ arg0.releaseLock();
+ };
+ imports.wbg.__wbg_resolve_0bf7c44d641804f9 = function(arg0) {
+ const ret = Promise.resolve(arg0);
+ return ret;
+ };
+ imports.wbg.__wbg_respond_88fe7338392675f2 = function() { return handleError(function (arg0, arg1) {
+ arg0.respond(arg1 >>> 0);
+ }, arguments) };
+ imports.wbg.__wbg_set_23d69db4e5c66a6e = function(arg0, arg1, arg2) {
+ arg0.set(arg1, arg2 >>> 0);
+ };
+ imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
+ const ret = arg1.stack;
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ };
+ imports.wbg.__wbg_static_accessor_GLOBAL_0be7472e492ad3e3 = function() {
+ const ret = typeof global === 'undefined' ? null : global;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_static_accessor_GLOBAL_THIS_1a6eb482d12c9bfb = function() {
+ const ret = typeof globalThis === 'undefined' ? null : globalThis;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_static_accessor_SELF_1dc398a895c82351 = function() {
+ const ret = typeof self === 'undefined' ? null : self;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_static_accessor_WINDOW_ae1c80c7eea8d64a = function() {
+ const ret = typeof window === 'undefined' ? null : window;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_then_0438fad860fe38e1 = function(arg0, arg1) {
+ const ret = arg0.then(arg1);
+ return ret;
+ };
+ imports.wbg.__wbg_then_0ffafeddf0e182a4 = function(arg0, arg1, arg2) {
+ const ret = arg0.then(arg1, arg2);
+ return ret;
+ };
+ imports.wbg.__wbg_toString_a491ccf7be1ca5c9 = function(arg0) {
+ const ret = arg0.toString();
+ return ret;
+ };
+ imports.wbg.__wbg_value_4c32fd138a88eee2 = function(arg0) {
+ const ret = arg0.value;
+ return ret;
+ };
+ imports.wbg.__wbg_view_a03cbb1d55c73e57 = function(arg0) {
+ const ret = arg0.view;
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+ };
+ imports.wbg.__wbg_write_0aea81ae26043440 = function(arg0, arg1) {
+ const ret = arg0.write(arg1);
+ return ret;
+ };
+ imports.wbg.__wbindgen_cb_drop = function(arg0) {
+ const obj = arg0.original;
+ if (obj.cnt-- == 1) {
+ obj.a = 0;
+ return true;
+ }
+ const ret = false;
+ return ret;
+ };
+ imports.wbg.__wbindgen_closure_wrapper4837 = function(arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 732, __wbg_adapter_26);
+ return ret;
+ };
+ imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
+ const ret = debugString(arg1);
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ };
+ imports.wbg.__wbindgen_init_externref_table = function() {
+ const table = wasm.__wbindgen_export_1;
+ const offset = table.grow(4);
+ table.set(0, undefined);
+ table.set(offset + 0, undefined);
+ table.set(offset + 1, null);
+ table.set(offset + 2, true);
+ table.set(offset + 3, false);
+ ;
+ };
+ imports.wbg.__wbindgen_is_function = function(arg0) {
+ const ret = typeof(arg0) === 'function';
+ return ret;
+ };
+ imports.wbg.__wbindgen_is_object = function(arg0) {
+ const val = arg0;
+ const ret = typeof(val) === 'object' && val !== null;
+ return ret;
+ };
+ imports.wbg.__wbindgen_is_undefined = function(arg0) {
+ const ret = arg0 === undefined;
+ return ret;
+ };
+ imports.wbg.__wbindgen_memory = function() {
+ const ret = wasm.memory;
+ return ret;
+ };
+ imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
+ const obj = arg1;
+ const ret = typeof(obj) === 'string' ? obj : undefined;
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ var len1 = WASM_VECTOR_LEN;
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
+ };
+ imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+ const ret = getStringFromWasm0(arg0, arg1);
+ return ret;
+ };
+ imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+ throw new Error(getStringFromWasm0(arg0, arg1));
+ };
+
+ return imports;
+}
+
+function __wbg_init_memory(imports, memory) {
+
+}
+
+function __wbg_finalize_init(instance, module) {
+ wasm = instance.exports;
+ __wbg_init.__wbindgen_wasm_module = module;
+ cachedDataViewMemory0 = null;
+ cachedUint8ArrayMemory0 = null;
+
+
+ wasm.__wbindgen_start();
+ return wasm;
+}
+
+function initSync(module) {
+ if (wasm !== undefined) return wasm;
+
+
+ if (typeof module !== 'undefined') {
+ if (Object.getPrototypeOf(module) === Object.prototype) {
+ ({module} = module)
+ } else {
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
+ }
+ }
+
+ const imports = __wbg_get_imports();
+
+ __wbg_init_memory(imports);
+
+ if (!(module instanceof WebAssembly.Module)) {
+ module = new WebAssembly.Module(module);
+ }
+
+ const instance = new WebAssembly.Instance(module, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+async function __wbg_init(module_or_path) {
+ if (wasm !== undefined) return wasm;
+
+
+ if (typeof module_or_path !== 'undefined') {
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
+ ({module_or_path} = module_or_path)
+ } else {
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
+ }
+ }
+
+ if (typeof module_or_path === 'undefined') {
+ module_or_path = new URL('demo_lsp_browser_bg.wasm', import.meta.url);
+ }
+ const imports = __wbg_get_imports();
+
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
+ module_or_path = fetch(module_or_path);
+ }
+
+ __wbg_init_memory(imports);
+
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+export { initSync };
+export default __wbg_init;
diff --git a/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm b/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm
new file mode 100644
index 0000000..f520d0d
Binary files /dev/null and b/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm differ
diff --git a/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm.d.ts b/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm.d.ts
new file mode 100644
index 0000000..dcaff6a
--- /dev/null
+++ b/packages/frontend/src/lib/wasm/demo_lsp_browser_bg.wasm.d.ts
@@ -0,0 +1,29 @@
+/* tslint:disable */
+/* eslint-disable */
+export const memory: WebAssembly.Memory;
+export const __wbg_serverconfig_free: (a: number, b: number) => void;
+export const serverconfig_new: (a: any, b: any) => number;
+export const serve: (a: number) => any;
+export const __wbg_intounderlyingbytesource_free: (a: number, b: number) => void;
+export const intounderlyingbytesource_type: (a: number) => number;
+export const intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number;
+export const intounderlyingbytesource_start: (a: number, b: any) => void;
+export const intounderlyingbytesource_pull: (a: number, b: any) => any;
+export const intounderlyingbytesource_cancel: (a: number) => void;
+export const __wbg_intounderlyingsource_free: (a: number, b: number) => void;
+export const intounderlyingsource_pull: (a: number, b: any) => any;
+export const intounderlyingsource_cancel: (a: number) => void;
+export const __wbg_intounderlyingsink_free: (a: number, b: number) => void;
+export const intounderlyingsink_write: (a: number, b: any) => any;
+export const intounderlyingsink_close: (a: number) => any;
+export const intounderlyingsink_abort: (a: number, b: any) => any;
+export const __externref_table_alloc: () => number;
+export const __wbindgen_export_1: WebAssembly.Table;
+export const __wbindgen_exn_store: (a: number) => void;
+export const __wbindgen_free: (a: number, b: number, c: number) => void;
+export const __wbindgen_malloc: (a: number, b: number) => number;
+export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
+export const __wbindgen_export_6: WebAssembly.Table;
+export const closure731_externref_shim: (a: number, b: number, c: any) => void;
+export const closure1257_externref_shim: (a: number, b: number, c: any, d: any) => void;
+export const __wbindgen_start: () => void;
diff --git a/packages/frontend/src/state/context.ts b/packages/frontend/src/state/context.ts
new file mode 100644
index 0000000..3368379
--- /dev/null
+++ b/packages/frontend/src/state/context.ts
@@ -0,0 +1,34 @@
+import { ExpNodeType, FolderType } from "@/types/explorer";
+import { LogType } from "@/types/log";
+import { Monaco } from "@monaco-editor/react";
+
+export const context = {
+ monaco: null as Monaco | null,
+ preferences: {
+ theme: "vs-dark",
+ fontSize: 14,
+ autoSave: true,
+ autoFormat: true,
+ },
+ currentFile: "home" as string | null,
+ logs: [] as LogType[],
+ tabs: new Set(),
+ files: {} as Record,
+ explorer: {
+ type: ExpNodeType.FOLDER,
+ open: true,
+ name: "explorer",
+ path: "explorer",
+ items: {
+ src: {
+ type: ExpNodeType.FOLDER,
+ open: true,
+ name: "src",
+ path: "explorer.items.src",
+ items: {},
+ },
+ },
+ } satisfies FolderType,
+};
+
+export type Context = typeof context;
diff --git a/packages/frontend/src/state/events.ts b/packages/frontend/src/state/events.ts
new file mode 100644
index 0000000..cc8b44c
--- /dev/null
+++ b/packages/frontend/src/state/events.ts
@@ -0,0 +1,127 @@
+import { ExpNodeType, FileType, FolderType } from "@/types/explorer";
+import set from "lodash/set";
+import get from "lodash/get";
+import unset from "lodash/unset";
+import { Context } from "./context";
+import { Monaco } from "@monaco-editor/react";
+import { createPath } from "./utils";
+import { MessageType } from "vscode-languageserver-protocol";
+import { nanoid } from "nanoid";
+
+export const events = {
+ toggleFolder: (context: Context, event: { path: string }) => {
+ const folder = get(context, event.path) as FolderType;
+ folder.open = !folder.open;
+ },
+ changeContent: (context: Context, event: { content: string }) => {
+ const path = context.currentFile;
+ if (path) {
+ context.files[path] = event.content;
+ }
+ },
+ setMonaco: (context: Context, event: { monaco: Monaco }) => {
+ context.monaco = event.monaco;
+ },
+ setCurrentPath: (context: Context, event: { path: string }) => {
+ context.currentFile = event.path;
+ events.addTab(context, { path: event.path });
+ },
+ removeNestedTabs: (context: Context, event: { path: string }) => {
+ const path = event.path;
+ const tabs = context.tabs;
+ for (const tab of tabs) {
+ if (tab.startsWith(path)) {
+ tabs.delete(tab);
+ }
+ }
+ },
+ addFile(context: Context, event: { basePath: string; name: string; content: string }) {
+ const path = createPath(event.basePath, event.name);
+ const file = {
+ type: ExpNodeType.FILE,
+ name: event.name,
+ path: path,
+ } satisfies FileType;
+
+ set(context, path, file);
+ context.files[path] = event.content;
+ context.currentFile = path;
+ events.addTab(context, { path });
+ },
+ addFiles(context: Context, event: { basePath: string; files: { name: string; content: string }[] }) {
+ for (const file of event.files) {
+ events.addFile(context, { basePath: event.basePath, name: file.name, content: file.content });
+ }
+ },
+ addFolder(context: Context, event: { basePath: string; name: string }) {
+ const folder = get(context, event.basePath) as FolderType;
+ folder.items[event.name] = {
+ items: {},
+ name: event.name,
+ open: true,
+ path: createPath(event.basePath, event.name),
+ type: ExpNodeType.FOLDER,
+ } satisfies FolderType;
+ },
+
+ deleteFile(context: Context, event: { path: string; basePath: string }) {
+ const folder = get(context, event.basePath) as FolderType;
+ const file = get(context, event.path) as FileType;
+
+ events.removeTab(context, { path: event.path });
+ delete folder.items[file.name];
+ delete context.files[event.path];
+ },
+ deleteFolder(context: Context, event: { path: string }) {
+ unset(context, event.path);
+ events.removeNestedTabs(context, event);
+ },
+ renameFile(context: Context, event: { path: string; name: string; basePath: string }) {
+ const newPath = createPath(event.basePath, event.name);
+ events.addFile(context, {
+ basePath: event.basePath,
+ name: event.name,
+ content: context.files[event.path],
+ });
+
+ if (context.currentFile === event.path) {
+ context.currentFile = newPath;
+ }
+
+ events.deleteFile(context, { path: event.path, basePath: event.basePath });
+ },
+ renameFolder(context: Context, event: { path: string; name: string; basePath: string }) {
+ const newPath = createPath(event.basePath, event.name);
+ const folder = get(context, event.path) as FolderType;
+ const newFolder = {
+ name: event.name,
+ items: folder.items,
+ open: folder.open,
+ path: newPath,
+ type: ExpNodeType.FOLDER,
+ } satisfies FolderType;
+
+ set(context, newPath, newFolder);
+ events.deleteFolder(context, { path: event.path });
+ },
+ removeTab(context: Context, event: { path: string }) {
+ context.tabs.delete(event.path);
+
+ if (event.path === context.currentFile) {
+ context.currentFile = Array.from(context.tabs).pop() || null;
+ }
+ },
+ addTab(context: Context, event: { path: string }) {
+ context.tabs.add(event.path);
+ },
+ addLog(context: Context, event: { logType: MessageType; message: string }) {
+ context.logs.push({
+ id: nanoid(),
+ type: event.logType,
+ message: event.message,
+ });
+ },
+ changeFontSize(context: Context, event: { fontSize: number }) {
+ context.preferences.fontSize = isNaN(event.fontSize) ? 14 : event.fontSize;
+ },
+};
diff --git a/packages/frontend/src/state/hooks.ts b/packages/frontend/src/state/hooks.ts
new file mode 100644
index 0000000..67ed44b
--- /dev/null
+++ b/packages/frontend/src/state/hooks.ts
@@ -0,0 +1,50 @@
+import { FileType, FolderType } from "@/types/explorer";
+import { useSelector } from "@xstate/store/react";
+import get from "lodash/get";
+import { store } from ".";
+
+export function useExplorer(path: string) {
+ const open = useSelector(store, (state) => get(state.context, path).open);
+ const name = useSelector(store, (state) => get(state.context, path).name);
+ useSelector(store, (state) => {
+ const folder = get(state.context, path) as FolderType;
+ const keys = Object.keys(folder.items);
+ return `${keys.length}:${keys.join(",")}`;
+ });
+ const state = store.getSnapshot().context;
+ const folder = get(state, path) as FolderType;
+
+ return { open, items: folder.items, name };
+}
+
+export function useExplorerItem(path: string) {
+ return useSelector(store, (state) => get(state.context, path)) as FileType;
+}
+
+export function useMonaco() {
+ return useSelector(store, (state) => state.context.monaco);
+}
+
+export function useCurrentFile() {
+ const path = useSelector(store, (state) => state.context.currentFile);
+ const file = useSelector(store, (state) => {
+ if (!path) {
+ return null;
+ }
+
+ return get(state.context, path) as FileType;
+ });
+
+ return file;
+}
+
+export function useFileContent() {
+ const path = useSelector(store, (state) => state.context.currentFile);
+ return useSelector(store, (state) => {
+ if (!path) {
+ return "";
+ }
+
+ return state.context.files[path];
+ });
+}
diff --git a/packages/frontend/src/state/index.ts b/packages/frontend/src/state/index.ts
new file mode 100644
index 0000000..0f97626
--- /dev/null
+++ b/packages/frontend/src/state/index.ts
@@ -0,0 +1,16 @@
+import { createStoreWithProducer } from "@xstate/store";
+import { createBrowserInspector } from "@statelyai/inspect";
+import { produce, enableMapSet } from "immer";
+import { context } from "./context";
+import { events } from "./events";
+
+enableMapSet();
+
+export const store = createStoreWithProducer(produce, {
+ context,
+ on: events,
+});
+
+const sub = store.inspect((inspectionEvent) => {
+ console.log(inspectionEvent);
+});
diff --git a/packages/frontend/src/state/inistate.ts b/packages/frontend/src/state/inistate.ts
new file mode 100644
index 0000000..41faba2
--- /dev/null
+++ b/packages/frontend/src/state/inistate.ts
@@ -0,0 +1,16 @@
+import { store } from ".";
+import { defaultCode } from "./initstate";
+
+function initState() {
+ store.send({
+ type: "addFile",
+ basePath: "explorer.items.src",
+ name: "main.sol",
+ content: defaultCode,
+ });
+
+ store.send({ type: "setCurrentPath", path: "explorer.items.src.items['main.sol']" });
+ store.send({ type: "setCurrentPath", path: "home" });
+}
+
+export default initState;
diff --git a/packages/frontend/src/state/initstate.ts b/packages/frontend/src/state/initstate.ts
new file mode 100644
index 0000000..8d762f3
--- /dev/null
+++ b/packages/frontend/src/state/initstate.ts
@@ -0,0 +1,21 @@
+export const defaultCode = `contract flipper {
+ bool private value;
+
+ /// Constructor that initializes the \`bool\` value to the given \`init_value\`.
+ constructor(bool initvalue) {
+ value = initvalue;
+ }
+
+ /// A message that can be called on instantiated contracts.
+ /// This one flips the value of the stored \`bool\` from \`true\`
+ /// to \`false\` and vice versa.
+ function flip() public {
+ value = !value;
+ }
+
+ /// Simply returns the current value of our \`bool\`.
+ function get() public view returns (bool) {
+ return value;
+ }
+}
+`;
\ No newline at end of file
diff --git a/packages/frontend/src/state/utils.ts b/packages/frontend/src/state/utils.ts
new file mode 100644
index 0000000..20a9567
--- /dev/null
+++ b/packages/frontend/src/state/utils.ts
@@ -0,0 +1,21 @@
+import { MessageType } from "vscode-languageserver-protocol";
+import { store } from ".";
+
+export function createPath(basePath: string, name: string) {
+ return basePath + ".items" + `['${name}']`;
+}
+
+export const logger = {
+ error(message: string) {
+ store.send({ type: "addLog", logType: MessageType.Error, message });
+ },
+ info(message: string) {
+ store.send({ type: "addLog", logType: MessageType.Info, message });
+ },
+ warning(message: string) {
+ store.send({ type: "addLog", logType: MessageType.Warning, message });
+ },
+ log(message: string) {
+ store.send({ type: "addLog", logType: MessageType.Log, message });
+ },
+};
diff --git a/packages/frontend/src/types/explorer.ts b/packages/frontend/src/types/explorer.ts
new file mode 100644
index 0000000..593bb78
--- /dev/null
+++ b/packages/frontend/src/types/explorer.ts
@@ -0,0 +1,22 @@
+import { editor } from "monaco-editor-core";
+
+export enum ExpNodeType {
+ FILE = "FILE",
+ FOLDER = "FOLDER",
+}
+
+export type ExplorerNode = FileType | FolderType;
+
+export interface FileType {
+ type: ExpNodeType.FILE;
+ path: string;
+ name: string;
+}
+
+export interface FolderType {
+ type: ExpNodeType.FOLDER;
+ path: string;
+ name: string;
+ open: boolean;
+ items: Record;
+}
diff --git a/packages/frontend/src/types/log.ts b/packages/frontend/src/types/log.ts
new file mode 100644
index 0000000..2b217a2
--- /dev/null
+++ b/packages/frontend/src/types/log.ts
@@ -0,0 +1,15 @@
+import { MessageType } from "vscode-languageserver-protocol";
+
+export interface LogType {
+ id: string;
+ type: MessageType;
+ message: string;
+}
+
+export const MessageTypeName = {
+ [MessageType.Error]: "Error",
+ [MessageType.Info]: "Info",
+ [MessageType.Warning]: "Warning",
+ [MessageType.Log]: "Log",
+ [MessageType.Debug]: "Debug",
+};
diff --git a/packages/frontend/tailwind.config.ts b/packages/frontend/tailwind.config.ts
new file mode 100644
index 0000000..c1ba796
--- /dev/null
+++ b/packages/frontend/tailwind.config.ts
@@ -0,0 +1,84 @@
+import type { Config } from "tailwindcss";
+
+export default {
+ darkMode: ["class"],
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ }
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0'
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)'
+ }
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)'
+ },
+ to: {
+ height: '0'
+ }
+ }
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out'
+ }
+ }
+ },
+ plugins: [require("tailwindcss-animate")],
+} satisfies Config;
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
new file mode 100644
index 0000000..421d132
--- /dev/null
+++ b/packages/frontend/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../../script.ts"],
+ "exclude": ["node_modules"]
+}