mirror of
https://github.com/decompme/decomp.me.git
synced 2026-02-14 10:34:12 -06:00
Add clangd language server support (#739)
* Initial clangd support Tidy up languageserver.ts Add CompilerCommand option * Codemirror tooltip newline handling * Force github login through redirects instead of popup window * Gracefully exit LS when scratch unloads * Break scratch language out to api, move language server integration to new hook * Fix broken import * Fix languageServer.ts import warnings (and rename languageserver.ts to languageServer.ts) * Format on Alt-Shift-F * Add default, site-wide .clang-format * Allow source to reference context definitions * Remove overzealous IIFEs * Clean up yaml imports, useLanguageServer state * Remove webpack yaml-specific config * Fix default-clang-format.yaml loading * Add editor option to enable / disable language server * Change language server setting text * Add ability to select small language server, version bump * EditorSettings clean * EditorSettings 2: Electric Boogaloo (sponsored by tailwind) * Fix tooltip appearance * Remove alignment settings from default-clang-format.yaml * Remove references to preferSmallLanguageServer --------- Co-authored-by: ConorBobbleHat <c.github@firstpartners.net> Co-authored-by: Mark Street <streetster@gmail.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import enum
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cache
|
||||
@@ -18,6 +17,7 @@ from coreapp.flags import (
|
||||
COMMON_MWCC_FLAGS,
|
||||
COMMON_GCC_SATURN_FLAGS,
|
||||
Flags,
|
||||
Language,
|
||||
)
|
||||
|
||||
from coreapp.platforms import (
|
||||
@@ -44,23 +44,6 @@ CONFIG_PY = "config.py"
|
||||
COMPILER_BASE_PATH: Path = settings.COMPILER_BASE_PATH
|
||||
|
||||
|
||||
class Language(enum.Enum):
|
||||
C = "C"
|
||||
OLD_CXX = "C++"
|
||||
CXX = "C++"
|
||||
PASCAL = "Pascal"
|
||||
ASSEMBLY = "Assembly"
|
||||
|
||||
def get_file_extension(self) -> str:
|
||||
return {
|
||||
Language.C: "c",
|
||||
Language.CXX: "cpp",
|
||||
Language.OLD_CXX: "c++",
|
||||
Language.PASCAL: "p",
|
||||
Language.ASSEMBLY: "s",
|
||||
}[self]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Compiler:
|
||||
id: str
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Union
|
||||
|
||||
ASMDIFF_FLAG_PREFIX = "-DIFF"
|
||||
|
||||
|
||||
class Language(enum.Enum):
|
||||
C = "C"
|
||||
OLD_CXX = "C++"
|
||||
CXX = "C++"
|
||||
PASCAL = "Pascal"
|
||||
ASSEMBLY = "Assembly"
|
||||
OBJECTIVE_C = "ObjectiveC"
|
||||
|
||||
def get_file_extension(self) -> str:
|
||||
return {
|
||||
Language.C: "c",
|
||||
Language.CXX: "cpp",
|
||||
Language.OLD_CXX: "c++",
|
||||
Language.PASCAL: "p",
|
||||
Language.ASSEMBLY: "s",
|
||||
Language.OBJECTIVE_C: "m",
|
||||
}[self]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Checkbox:
|
||||
id: str
|
||||
@@ -30,13 +50,31 @@ class FlagSet:
|
||||
}
|
||||
|
||||
|
||||
Flags = List[Union[Checkbox, FlagSet]]
|
||||
@dataclass(frozen=True)
|
||||
class LanguageFlagSet:
|
||||
id: str
|
||||
flags: Dict[str, Language]
|
||||
|
||||
def to_json(self) -> Dict[str, Union[str, List[str]]]:
|
||||
# To the client, we're a regular FlagSet - the extra metadata we carry
|
||||
# is purely for the backend to determine the scratch's language
|
||||
return {
|
||||
"type": "flagset",
|
||||
"id": self.id,
|
||||
"flags": list(self.flags.keys()),
|
||||
}
|
||||
|
||||
|
||||
Flags = List[Union[Checkbox, FlagSet, LanguageFlagSet]]
|
||||
|
||||
COMMON_ARMCC_FLAGS: Flags = [
|
||||
FlagSet(
|
||||
id="armcc_opt_level", flags=["-O0", "-O1", "-O2", "-O3", "-Ospace", "-Otime"]
|
||||
),
|
||||
FlagSet(id="armcc_language", flags=["--c90", "--c99", "--cpp"]),
|
||||
LanguageFlagSet(
|
||||
id="armcc_language",
|
||||
flags={"--c90": Language.C, "--c99": Language.C, "--cpp": Language.CXX},
|
||||
),
|
||||
FlagSet(id="armcc_instset", flags=["--arm", "--thumb"]),
|
||||
Checkbox(id="armcc_debug", flag="--debug"),
|
||||
]
|
||||
@@ -46,7 +84,9 @@ COMMON_CLANG_FLAGS: Flags = [
|
||||
id="clang_opt_level", flags=["-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os", "-Oz"]
|
||||
),
|
||||
FlagSet(id="clang_debug_level", flags=["-g0", "-g1", "-g2", "-g3"]),
|
||||
FlagSet(id="clang_language", flags=["-x c++", "-x c"]),
|
||||
LanguageFlagSet(
|
||||
id="clang_language", flags={"-x c++": Language.CXX, "-x c": Language.C}
|
||||
),
|
||||
FlagSet(
|
||||
id="clang_language_standard",
|
||||
flags=[
|
||||
@@ -136,9 +176,15 @@ COMMON_MWCC_FLAGS: Flags = [
|
||||
id="mwcc_string_constant_options",
|
||||
flags=["-str reuse", "-str pool", "-str readonly", "-str reuse,pool,readonly"],
|
||||
),
|
||||
FlagSet(
|
||||
LanguageFlagSet(
|
||||
id="mwcc_language",
|
||||
flags=["-lang=c", "-lang=c++", "-lang=c99", "-lang=ec++", "-lang=objc"],
|
||||
flags={
|
||||
"-lang=c": Language.C,
|
||||
"-lang=c++": Language.CXX,
|
||||
"-lang=c99": Language.C,
|
||||
"-lang=ec++": Language.CXX,
|
||||
"-lang=objc": Language.OBJECTIVE_C,
|
||||
},
|
||||
),
|
||||
FlagSet(id="mwcc_char_signedness", flags=["-char signed", "-char unsigned"]),
|
||||
Checkbox(id="mwcc_cpp_exceptions_off", flag="-Cpp_exceptions off"),
|
||||
|
||||
@@ -16,6 +16,9 @@ from .models.profile import Profile
|
||||
from .models.project import Project, ProjectFunction, ProjectImportConfig, ProjectMember
|
||||
from .models.scratch import CompilerConfig, Scratch
|
||||
|
||||
from .flags import LanguageFlagSet
|
||||
from . import compilers
|
||||
|
||||
|
||||
def serialize_profile(
|
||||
request: Request, profile: Profile, small: bool = False
|
||||
@@ -142,6 +145,7 @@ class ScratchSerializer(serializers.HyperlinkedModelSerializer):
|
||||
context = serializers.CharField(allow_blank=True, trim_whitespace=False) # type: ignore
|
||||
project = serializers.SerializerMethodField()
|
||||
project_function = serializers.SerializerMethodField()
|
||||
language = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Scratch
|
||||
@@ -183,6 +187,38 @@ class ScratchSerializer(serializers.HyperlinkedModelSerializer):
|
||||
)
|
||||
return None
|
||||
|
||||
def get_language(self, scratch: Scratch) -> Optional[str]:
|
||||
"""
|
||||
Strategy for extracting a scratch's language:
|
||||
- If the scratch's compiler has a LanguageFlagSet in its flags, attempt to match a language flag against that
|
||||
- Otherwise, fallback to the compiler's default language
|
||||
"""
|
||||
compiler = compilers.from_id(scratch.compiler)
|
||||
language_flag_set = next(
|
||||
iter([i for i in compiler.flags if isinstance(i, LanguageFlagSet)]),
|
||||
None,
|
||||
)
|
||||
|
||||
if language_flag_set:
|
||||
language = next(
|
||||
iter(
|
||||
[
|
||||
language
|
||||
for (flag, language) in language_flag_set.flags.items()
|
||||
if flag in scratch.compiler_flags
|
||||
]
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if language:
|
||||
return language.value
|
||||
|
||||
# If we're here, either the compiler doesn't have a LanguageFlagSet, or the scratch doesn't
|
||||
# have a flag within it.
|
||||
# Either way: fall back to the compiler default.
|
||||
return compiler.language.value
|
||||
|
||||
|
||||
class TerseScratchSerializer(ScratchSerializer):
|
||||
owner = TerseProfileField(read_only=True)
|
||||
@@ -198,6 +234,7 @@ class TerseScratchSerializer(ScratchSerializer):
|
||||
"creation_time",
|
||||
"platform",
|
||||
"compiler",
|
||||
"language",
|
||||
"name",
|
||||
"score",
|
||||
"max_score",
|
||||
|
||||
@@ -12,7 +12,8 @@ from django.test.testcases import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
from coreapp.compilers import DummyCompiler, Language
|
||||
from coreapp.compilers import DummyCompiler
|
||||
from coreapp.flags import Language
|
||||
from coreapp.sandbox import Sandbox
|
||||
from coreapp import compilers, platforms
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from rest_framework.viewsets import GenericViewSet
|
||||
from coreapp import compilers, platforms
|
||||
from ..compiler_wrapper import CompilationResult, CompilerWrapper, DiffResult
|
||||
from ..decompiler_wrapper import DecompilerWrapper
|
||||
from ..compilers import Language
|
||||
from ..flags import Language
|
||||
|
||||
from ..decorators.django import condition
|
||||
|
||||
|
||||
@@ -72,7 +72,14 @@ let app = withPlausibleProxy({
|
||||
key: "X-DNS-Prefetch-Control",
|
||||
value: "on",
|
||||
},
|
||||
|
||||
{
|
||||
key: "Cross-Origin-Opener-Policy",
|
||||
value:"same-origin",
|
||||
},
|
||||
{
|
||||
key: "Cross-Origin-Embedder-Policy",
|
||||
value: "require-corp",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -83,6 +90,27 @@ let app = withPlausibleProxy({
|
||||
use: ["@svgr/webpack"],
|
||||
})
|
||||
|
||||
// @open-rpc/client-js brings in some dependencies which, in turn, have optional dependencies.
|
||||
// This confuses the heck out of webpack, so tell it should just sub in a CommonJS-style "require" statement
|
||||
// instead (which will fail and trigger the fallback at runtime)
|
||||
// https://stackoverflow.com/questions/58697934/webpack-how-do-you-require-an-optional-dependency-in-bundle-saslprep
|
||||
config.externals.push({
|
||||
"encoding": "commonjs encoding",
|
||||
"bufferutil": "commonjs bufferutil",
|
||||
"utf-8-validate": "commonjs utf-8-validate",
|
||||
})
|
||||
|
||||
// All of the vscode-* packages (jsonrpc, languageserver-protocol, etc.) are distributed as UMD modules.
|
||||
// This also leaves webpack with no idea how to handle require statements.
|
||||
// umd-compat-loader strips away the hedaer UMD adds to allow browsers to parse ES modules
|
||||
// and just treats the importee as an ES module.
|
||||
config.module.rules.push({
|
||||
"test": /node_modules[\\|/](vscode-.*)/,
|
||||
"use": {
|
||||
"loader": "umd-compat-loader",
|
||||
},
|
||||
})
|
||||
|
||||
return config
|
||||
},
|
||||
images: {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"clean": "rm -rf .next node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clangd-wasm/clangd-wasm": "15.0.7-dev5",
|
||||
"@codemirror/autocomplete": "^6.6.0",
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
@@ -20,6 +21,7 @@
|
||||
"@codemirror/view": "^6.10.1",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/highlight": "^1.1.4",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@primer/octicons-react": "^18.3.0",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
@@ -49,7 +51,8 @@
|
||||
"sharp": "^0.32.1",
|
||||
"swr": "^1.3.0",
|
||||
"use-debounce": "^9.0.4",
|
||||
"use-persisted-state": "^0.3.3"
|
||||
"use-persisted-state": "^0.3.3",
|
||||
"vscode-languageserver-protocol": "^3.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
@@ -86,6 +89,7 @@
|
||||
"stylelint-config-standard-scss": "^9.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.0.4",
|
||||
"umd-compat-loader": "^2.1.2",
|
||||
"webpack": "^5.81.0"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function WelcomeInfo() {
|
||||
<ArrowRightIcon />
|
||||
</Button>
|
||||
</Link>
|
||||
<GitHubLoginButton popup />
|
||||
<GitHubLoginButton />
|
||||
</div>
|
||||
<div className="my-6 hidden sm:block">
|
||||
<SiteStats />
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function Page() {
|
||||
<p className="py-4">
|
||||
You can try again by clicking the button below.
|
||||
</p>
|
||||
<GitHubLoginButton popup={false} />
|
||||
<GitHubLoginButton />
|
||||
</div> : code ? <div className="flex items-center justify-center gap-4 py-8 text-2xl font-medium text-gray-12">
|
||||
<LoadingSpinner width={32} className="animate-spin" />
|
||||
Signing in...
|
||||
@@ -72,7 +72,7 @@ export default function Page() {
|
||||
<p>
|
||||
Sign in to decomp.me
|
||||
</p>
|
||||
<GitHubLoginButton popup={false} />
|
||||
<GitHubLoginButton />
|
||||
</div>}
|
||||
</main>
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function UserState() {
|
||||
{isAnonUser(user) ? "You appear as" : "Signed in as"} <UserMention user={user} />
|
||||
</p>
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
{isAnonUser(user) && <GitHubLoginButton popup />}
|
||||
{isAnonUser(user) && <GitHubLoginButton />}
|
||||
<SignOutButton />
|
||||
</div>
|
||||
</div> : <div className="flex animate-pulse items-center gap-2">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import LoadingSpinner from "@/components/loading.svg"
|
||||
import * as settings from "@/lib/settings"
|
||||
|
||||
import Checkbox from "../Checkbox"
|
||||
@@ -9,6 +11,31 @@ import SliderField from "../SliderField"
|
||||
export default function EditorSettings() {
|
||||
const [autoRecompile, setAutoRecompile] = settings.useAutoRecompileSetting()
|
||||
const [autoRecompileDelay, setAutoRecompileDelay] = settings.useAutoRecompileDelaySetting()
|
||||
const [languageServerEnabled, setLanguageServerEnabled] = settings.useLanguageServerEnabled()
|
||||
|
||||
const [downloadingLanguageServer, setDownloadingLanguageServer] = useState(false)
|
||||
|
||||
const isInitialMount = useRef(true)
|
||||
|
||||
useEffect(() => {
|
||||
// Prevent the language server binary from being downloaded if the user has it enabled, then enters settings
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false
|
||||
return
|
||||
}
|
||||
|
||||
if (languageServerEnabled) {
|
||||
setDownloadingLanguageServer(true)
|
||||
|
||||
import("@clangd-wasm/clangd-wasm").then(({ ClangdStdioTransport }) => {
|
||||
// We don't need to do anything with the result of this fetch - all this
|
||||
// is is a way to make sure the wasm file ends up in the browser's cache.
|
||||
fetch(ClangdStdioTransport.getDefaultWasmURL(false))
|
||||
.then(res => res.blob())
|
||||
.then(() => setDownloadingLanguageServer(false))
|
||||
})
|
||||
}
|
||||
}, [languageServerEnabled])
|
||||
|
||||
return <>
|
||||
<Section title="Automatic compilation">
|
||||
@@ -32,5 +59,15 @@ export default function EditorSettings() {
|
||||
</div>
|
||||
</Checkbox>
|
||||
</Section>
|
||||
<Section title="Language server">
|
||||
<Checkbox
|
||||
checked={languageServerEnabled}
|
||||
onChange={setLanguageServerEnabled}
|
||||
label="Enable language server"
|
||||
description="Enable editor features such as code completion, error checking, and formatting via clangd and WebAssembly magic. WARNING: enabling will incur a one time ~13MB download, and bump up resource usage during editing.">
|
||||
|
||||
{downloadingLanguageServer && <div className="flex gap-2 p-4"><LoadingSpinner width="24px" /> Downloading...</div>}
|
||||
</Checkbox>
|
||||
</Section>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -14,12 +14,17 @@
|
||||
}
|
||||
|
||||
:global(.cm-gutters),
|
||||
:global(.cm-content) {
|
||||
:global(.cm-content),
|
||||
:global(.cm-tooltip-hover) {
|
||||
font-size: var(--cm-font-size, 0.8rem);
|
||||
font-family: var(--monospace);
|
||||
line-height: var(--code-line-height, 1.5);
|
||||
}
|
||||
|
||||
:global(.cm-tooltip-section) {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
:global(.cm-gutters) {
|
||||
color: var(--code-foreground) !important;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Button from "./Button"
|
||||
|
||||
const DEFAULT_SCOPE_STR = ""
|
||||
|
||||
export default function GitHubLoginButton({ label, popup, className }: { label?: string, popup: boolean, className?: string }) {
|
||||
export default function GitHubLoginButton({ label, className }: { label?: string, className?: string }) {
|
||||
const user = useThisUser()
|
||||
|
||||
if (user && !isAnonUser(user)) {
|
||||
@@ -18,7 +18,7 @@ export default function GitHubLoginButton({ label, popup, className }: { label?:
|
||||
}
|
||||
|
||||
if (isGitHubLoginSupported()) {
|
||||
return <Button className={className} onClick={() => showGitHubLoginWindow(popup, DEFAULT_SCOPE_STR)}>
|
||||
return <Button className={className} onClick={() => showGitHubLoginWindow(DEFAULT_SCOPE_STR)}>
|
||||
<MarkGithubIcon size={16} /> {label ?? "Sign in with GitHub"}
|
||||
</Button>
|
||||
} else {
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function LoginState({ className }: { className?: string }) {
|
||||
}
|
||||
|
||||
if (api.isAnonUser(user)) {
|
||||
return <GitHubLoginButton popup label="Sign in" />
|
||||
return <GitHubLoginButton label="Sign in" />
|
||||
}
|
||||
|
||||
return <button
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function UserMenuItems() {
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<GitHubLoginButton popup />
|
||||
<GitHubLoginButton />
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as api from "@/lib/api"
|
||||
import basicSetup from "@/lib/codemirror/basic-setup"
|
||||
import useCompareExtension from "@/lib/codemirror/useCompareExtension"
|
||||
import { useSize } from "@/lib/hooks"
|
||||
import { useAutoRecompileSetting, useAutoRecompileDelaySetting } from "@/lib/settings"
|
||||
import { useAutoRecompileSetting, useAutoRecompileDelaySetting, useLanguageServerEnabled } from "@/lib/settings"
|
||||
|
||||
import CompilerOpts from "../compiler/CompilerOpts"
|
||||
import CustomLayout, { activateTabInLayout, Layout } from "../CustomLayout"
|
||||
@@ -20,6 +20,7 @@ import { Tab, TabCloseButton } from "../Tabs"
|
||||
import AboutScratch from "./AboutScratch"
|
||||
import DecompilationPanel from "./DecompilePanel"
|
||||
import FamilyPanel from "./FamilyPanel"
|
||||
import useLanguageServer from "./hooks/useLanguageServer"
|
||||
import styles from "./Scratch.module.scss"
|
||||
import ScratchMatchBanner from "./ScratchMatchBanner"
|
||||
import ScratchToolbar from "./ScratchToolbar"
|
||||
@@ -129,6 +130,7 @@ export default function Scratch({
|
||||
|
||||
const [autoRecompileSetting] = useAutoRecompileSetting()
|
||||
const [autoRecompileDelaySetting] = useAutoRecompileDelaySetting()
|
||||
const [languageServerEnabledSetting] = useLanguageServerEnabled()
|
||||
const { compilation, isCompiling, isCompilationOld, compile } = api.useCompilation(scratch, autoRecompileSetting, autoRecompileDelaySetting, initialCompilation)
|
||||
const userIsYou = api.useUserIsYou()
|
||||
const [selectedSourceLine, setSelectedSourceLine] = useState<number | null>()
|
||||
@@ -146,6 +148,8 @@ export default function Scratch({
|
||||
const sourceCompareExtension = useCompareExtension(sourceEditor, shouldCompare ? parentScratch?.source_code : undefined)
|
||||
const contextCompareExtension = useCompareExtension(contextEditor, shouldCompare ? parentScratch?.context : undefined)
|
||||
|
||||
const [saveSource, saveContext] = useLanguageServer(languageServerEnabledSetting, scratch, sourceEditor, contextEditor)
|
||||
|
||||
// TODO: CustomLayout should handle adding/removing tabs
|
||||
const [decompilationTabEnabled, setDecompilationTabEnabled] = useState(false)
|
||||
useEffect(() => {
|
||||
@@ -177,7 +181,10 @@ export default function Scratch({
|
||||
key={id}
|
||||
tabKey={id}
|
||||
label="Source code"
|
||||
onSelect={() => sourceEditor.current?.focus?.()}
|
||||
onSelect={() => {
|
||||
sourceEditor.current?.focus?.()
|
||||
saveContext()
|
||||
}}
|
||||
>
|
||||
<CodeMirror
|
||||
viewRef={sourceEditor}
|
||||
@@ -197,7 +204,10 @@ export default function Scratch({
|
||||
tabKey={id}
|
||||
label="Context"
|
||||
className={styles.context}
|
||||
onSelect={() => contextEditor.current?.focus?.()}
|
||||
onSelect={() => {
|
||||
contextEditor.current?.focus?.()
|
||||
saveSource()
|
||||
}}
|
||||
>
|
||||
<CodeMirror
|
||||
viewRef={contextEditor}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Based on https://github.com/pmret/papermario/blob/main/.clang-format
|
||||
|
||||
DisableFormat: true # clang-format supports many filetypes
|
||||
---
|
||||
Language: Cpp
|
||||
DisableFormat: false
|
||||
BasedOnStyle: WebKit
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
# Place opening brace on next line if the statement is multi-line
|
||||
# e.g.
|
||||
# if (very
|
||||
# long
|
||||
# condition)
|
||||
# {
|
||||
AfterControlStatement: MultiLine
|
||||
|
||||
IndentCaseLabels: true
|
||||
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
|
||||
ExperimentalAutoDetectBinPacking: true
|
||||
BinPackParameters: false
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
---
|
||||
|
||||
...
|
||||
131
frontend/src/components/Scratch/hooks/useLanguageServer.ts
Normal file
131
frontend/src/components/Scratch/hooks/useLanguageServer.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { MutableRefObject, useEffect, useState } from "react"
|
||||
|
||||
import type { ClangdStdioTransport, CompileCommands } from "@clangd-wasm/clangd-wasm"
|
||||
import { StateEffect } from "@codemirror/state"
|
||||
import { EditorView } from "codemirror"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
import { LanguageServerClient, languageServerWithTransport } from "@/lib/codemirror/languageServer"
|
||||
|
||||
export default function useLanguageServer(enabled: boolean, scratch: api.Scratch, sourceEditor: MutableRefObject<EditorView>, contextEditor: MutableRefObject<EditorView>) {
|
||||
const [initialScratchState, setInitialScratchState] = useState<api.Scratch>(undefined)
|
||||
const [defaultClangFormat, setDefaultClangFormat] = useState<string>(undefined)
|
||||
|
||||
const [ClangdStdioTransportModule, setClangdStdioTransportModule] = useState<typeof ClangdStdioTransport>(undefined)
|
||||
|
||||
const [saveSource, setSaveSource] = useState<(string) => Promise<void>>(undefined)
|
||||
const [saveContext, setSaveContext] = useState<(string) => Promise<void>>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
const loadClangdModule = async () => {
|
||||
if (!enabled) return
|
||||
if (!(scratch.language == "C" || scratch.language == "C++")) return
|
||||
|
||||
const { ClangdStdioTransport } = await import("@clangd-wasm/clangd-wasm")
|
||||
setClangdStdioTransportModule(() => ClangdStdioTransport)
|
||||
}
|
||||
|
||||
loadClangdModule()
|
||||
}, [scratch.language, enabled])
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialScratchState) {
|
||||
setInitialScratchState(scratch)
|
||||
}
|
||||
}, [scratch, initialScratchState])
|
||||
|
||||
useEffect(() => {
|
||||
fetch(new URL("./default-clang-format.yaml", import.meta.url))
|
||||
.then(res => res.text())
|
||||
.then(setDefaultClangFormat)
|
||||
}, [])
|
||||
|
||||
// We break this out into a seperate effect from the module loading
|
||||
// because if we had _lsClient defined inside an async function, we wouldn't be
|
||||
// able to reference it inside of the destructor.
|
||||
useEffect(() => {
|
||||
if (!ClangdStdioTransportModule) return
|
||||
if (!initialScratchState) return
|
||||
if (!defaultClangFormat) return
|
||||
|
||||
const languageId = {
|
||||
"C": "c",
|
||||
"C++": "cpp",
|
||||
}[initialScratchState.language]
|
||||
|
||||
const sourceFilename = `source.${languageId}`
|
||||
const contextFilename = `context.${languageId}`
|
||||
|
||||
const compileCommands: CompileCommands = [
|
||||
{
|
||||
directory: "/",
|
||||
file: sourceFilename,
|
||||
arguments: ["clang", sourceFilename, "-include", contextFilename],
|
||||
},
|
||||
]
|
||||
|
||||
const initialFileState = {
|
||||
".clang-format": defaultClangFormat,
|
||||
}
|
||||
|
||||
initialFileState[sourceFilename] = initialScratchState.source_code
|
||||
initialFileState[contextFilename] = initialScratchState.context
|
||||
|
||||
const _lsClient = new LanguageServerClient({
|
||||
transport: new ClangdStdioTransportModule({
|
||||
compileCommands,
|
||||
initialFileState,
|
||||
useSmallBinary: false,
|
||||
}),
|
||||
|
||||
rootUri: "file:///",
|
||||
workspaceFolders: null,
|
||||
documentUri: null,
|
||||
languageId,
|
||||
})
|
||||
|
||||
const [sourceLsExtension, _saveSource] = languageServerWithTransport({
|
||||
client: _lsClient,
|
||||
transport: null,
|
||||
rootUri: "file:///",
|
||||
workspaceFolders: null,
|
||||
documentUri: `file:///${sourceFilename}`,
|
||||
languageId,
|
||||
})
|
||||
|
||||
const [contextLsExtension, _saveContext] = languageServerWithTransport({
|
||||
client: _lsClient,
|
||||
transport: null,
|
||||
rootUri: "file:///",
|
||||
workspaceFolders: null,
|
||||
documentUri: `file:///${contextFilename}`,
|
||||
languageId,
|
||||
})
|
||||
|
||||
// TODO: return the codemirror extensions instead of hotpatching them in?
|
||||
// Given the async nature of the extension being ready, it'd require updating the Codemirror
|
||||
// component to support inserting extensions when the extension prop changes
|
||||
sourceEditor.current?.dispatch({ effects: StateEffect.appendConfig.of(sourceLsExtension) })
|
||||
contextEditor.current?.dispatch({ effects: StateEffect.appendConfig.of(contextLsExtension) })
|
||||
|
||||
setSaveSource(() => _saveSource)
|
||||
setSaveContext(() => _saveContext)
|
||||
|
||||
return () => {
|
||||
_lsClient.exit()
|
||||
}
|
||||
|
||||
}, [ClangdStdioTransportModule, initialScratchState, defaultClangFormat, sourceEditor, contextEditor])
|
||||
|
||||
const saveSourceRet = () => {
|
||||
if (saveSource)
|
||||
saveSource(scratch.source_code)
|
||||
}
|
||||
|
||||
const saveContextRet = () => {
|
||||
if (saveContext)
|
||||
saveContext(scratch.context)
|
||||
}
|
||||
|
||||
return [saveSourceRet, saveContextRet]
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export interface TerseScratch {
|
||||
last_updated: string
|
||||
compiler: string
|
||||
platform: string
|
||||
language: string
|
||||
score: number // -1 = doesn't compile
|
||||
max_score: number
|
||||
project: string
|
||||
|
||||
659
frontend/src/lib/codemirror/languageServer.ts
Normal file
659
frontend/src/lib/codemirror/languageServer.ts
Normal file
@@ -0,0 +1,659 @@
|
||||
/* Originally adapted from https://github.com/FurqanSoftware/codemirror-languageserver */
|
||||
|
||||
import type { ClangdStdioTransport } from "@clangd-wasm/clangd-wasm"
|
||||
import { autocompletion } from "@codemirror/autocomplete"
|
||||
import type {
|
||||
Completion,
|
||||
CompletionContext,
|
||||
CompletionResult,
|
||||
} from "@codemirror/autocomplete"
|
||||
import { setDiagnostics } from "@codemirror/lint"
|
||||
import { Extension, Facet } from "@codemirror/state"
|
||||
import type { Text } from "@codemirror/state"
|
||||
import { EditorView, ViewPlugin, Tooltip, hoverTooltip, keymap } from "@codemirror/view"
|
||||
import type { ViewUpdate, PluginValue } from "@codemirror/view"
|
||||
import {
|
||||
RequestManager,
|
||||
Client,
|
||||
} from "@open-rpc/client-js"
|
||||
import { Transport } from "@open-rpc/client-js/build/transports/Transport"
|
||||
import {
|
||||
DiagnosticSeverity,
|
||||
CompletionItemKind,
|
||||
CompletionTriggerKind,
|
||||
} from "vscode-languageserver-protocol"
|
||||
import type * as LSP from "vscode-languageserver-protocol"
|
||||
|
||||
const timeout = 10000
|
||||
const changesDelay = 500
|
||||
|
||||
const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
) as Record<CompletionItemKind, string>
|
||||
|
||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, "")
|
||||
|
||||
const client = Facet.define<LanguageServerClient, LanguageServerClient>({ combine: useLast })
|
||||
const documentUri = Facet.define<string, string>({ combine: useLast })
|
||||
const languageId = Facet.define<string, string>({ combine: useLast })
|
||||
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
|
||||
// Client to server then server to client
|
||||
interface LSPRequestMap {
|
||||
initialize: [LSP.InitializeParams, LSP.InitializeResult]
|
||||
"textDocument/hover": [LSP.HoverParams, LSP.Hover]
|
||||
"textDocument/completion": [
|
||||
LSP.CompletionParams,
|
||||
LSP.CompletionItem[] | LSP.CompletionList | null,
|
||||
]
|
||||
"textDocument/formatting": [LSP.DocumentFormattingParams, LSP.TextEdit[] | null]
|
||||
"shutdown": [null, null]
|
||||
}
|
||||
|
||||
// Client to server
|
||||
interface LSPNotifyMap {
|
||||
initialized: LSP.InitializedParams
|
||||
"textDocument/didChange": LSP.DidChangeTextDocumentParams
|
||||
"textDocument/didOpen": LSP.DidOpenTextDocumentParams
|
||||
"textDocument/didSave": LSP.DidSaveTextDocumentParams
|
||||
"exit": null
|
||||
}
|
||||
|
||||
// Server to client
|
||||
interface LSPEventMap {
|
||||
"textDocument/publishDiagnostics": LSP.PublishDiagnosticsParams
|
||||
}
|
||||
|
||||
type Notification = {
|
||||
[key in keyof LSPEventMap]: {
|
||||
jsonrpc: "2.0"
|
||||
id?: null | undefined
|
||||
method: key
|
||||
params: LSPEventMap[key]
|
||||
};
|
||||
}[keyof LSPEventMap];
|
||||
|
||||
class LanguageServerClient {
|
||||
private rootUri: string
|
||||
private workspaceFolders: LSP.WorkspaceFolder[]
|
||||
private autoClose?: boolean
|
||||
|
||||
private transport: Transport
|
||||
private requestManager: RequestManager
|
||||
private client: Client
|
||||
|
||||
public ready: boolean
|
||||
public capabilities: LSP.ServerCapabilities<any>
|
||||
|
||||
private plugins: LanguageServerPlugin[]
|
||||
|
||||
public initializePromise: Promise<void>
|
||||
|
||||
constructor(options: LanguageServerClientOptions) {
|
||||
this.rootUri = options.rootUri
|
||||
this.workspaceFolders = options.workspaceFolders
|
||||
this.autoClose = options.autoClose
|
||||
this.plugins = []
|
||||
this.transport = options.transport
|
||||
|
||||
this.requestManager = new RequestManager([this.transport])
|
||||
this.client = new Client(this.requestManager)
|
||||
|
||||
this.client.onNotification(data => {
|
||||
this.processNotification(data as any)
|
||||
})
|
||||
|
||||
this.initializePromise = this.initialize()
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
const { capabilities } = await this.request("initialize", {
|
||||
capabilities: {
|
||||
textDocument: {
|
||||
hover: {
|
||||
dynamicRegistration: true,
|
||||
contentFormat: ["plaintext", "markdown"],
|
||||
},
|
||||
moniker: {},
|
||||
synchronization: {
|
||||
dynamicRegistration: true,
|
||||
willSave: false,
|
||||
didSave: true,
|
||||
willSaveWaitUntil: false,
|
||||
},
|
||||
completion: {
|
||||
dynamicRegistration: true,
|
||||
completionItem: {
|
||||
snippetSupport: false,
|
||||
commitCharactersSupport: true,
|
||||
documentationFormat: ["plaintext", "markdown"],
|
||||
deprecatedSupport: false,
|
||||
preselectSupport: false,
|
||||
},
|
||||
contextSupport: false,
|
||||
},
|
||||
signatureHelp: {
|
||||
dynamicRegistration: true,
|
||||
signatureInformation: {
|
||||
documentationFormat: ["plaintext", "markdown"],
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
definition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
typeDefinition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
implementation: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
formatting: {
|
||||
dynamicRegistration: true,
|
||||
},
|
||||
},
|
||||
workspace: {
|
||||
didChangeConfiguration: {
|
||||
dynamicRegistration: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
initializationOptions: null,
|
||||
processId: null,
|
||||
rootUri: this.rootUri,
|
||||
workspaceFolders: this.workspaceFolders,
|
||||
}, timeout * 3)
|
||||
this.capabilities = capabilities
|
||||
this.notify("initialized", {})
|
||||
this.ready = true
|
||||
}
|
||||
|
||||
async exit() {
|
||||
await this.request("shutdown", null, timeout)
|
||||
return this.notify("exit", null)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.client.close()
|
||||
}
|
||||
|
||||
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
||||
return this.notify("textDocument/didOpen", params)
|
||||
}
|
||||
|
||||
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
||||
return this.notify("textDocument/didChange", params)
|
||||
}
|
||||
|
||||
async textDocumentHover(params: LSP.HoverParams) {
|
||||
return await this.request("textDocument/hover", params, timeout)
|
||||
}
|
||||
|
||||
async textDocumentCompletion(params: LSP.CompletionParams) {
|
||||
return await this.request("textDocument/completion", params, timeout)
|
||||
}
|
||||
|
||||
async textDocumentFormatting(params: LSP.DocumentFormattingParams) {
|
||||
return await this.request("textDocument/formatting", params, timeout)
|
||||
}
|
||||
|
||||
async textDocumentDidSave(params: LSP.DidSaveTextDocumentParams) {
|
||||
return this.notify("textDocument/didSave", params)
|
||||
}
|
||||
|
||||
transportWriteFile(filename: string, contents: string) {
|
||||
const transport = <ClangdStdioTransport> this.transport
|
||||
transport.module?.FS?.writeFile(filename, contents)
|
||||
}
|
||||
|
||||
attachPlugin(plugin: LanguageServerPlugin) {
|
||||
this.plugins.push(plugin)
|
||||
}
|
||||
|
||||
detachPlugin(plugin: LanguageServerPlugin) {
|
||||
const i = this.plugins.indexOf(plugin)
|
||||
if (i === -1) return
|
||||
this.plugins.splice(i, 1)
|
||||
if (this.autoClose) this.close()
|
||||
}
|
||||
|
||||
private request<K extends keyof LSPRequestMap>(
|
||||
method: K,
|
||||
params: LSPRequestMap[K][0],
|
||||
timeout: number
|
||||
): Promise<LSPRequestMap[K][1]> {
|
||||
return this.client.request({ method, params }, timeout)
|
||||
}
|
||||
|
||||
private notify<K extends keyof LSPNotifyMap>(
|
||||
method: K,
|
||||
params: LSPNotifyMap[K]
|
||||
): Promise<LSPNotifyMap[K]> {
|
||||
return this.client.notify({ method, params })
|
||||
}
|
||||
|
||||
private processNotification(notification: Notification) {
|
||||
for (const plugin of this.plugins)
|
||||
plugin.processNotification(notification)
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageServerPlugin implements PluginValue {
|
||||
public client: LanguageServerClient
|
||||
|
||||
private documentUri: string
|
||||
private languageId: string
|
||||
private documentVersion: number
|
||||
|
||||
private changesTimeout: number
|
||||
|
||||
constructor(private view: EditorView) {
|
||||
this.client = this.view.state.facet(client)
|
||||
this.documentUri = this.view.state.facet(documentUri)
|
||||
this.languageId = this.view.state.facet(languageId)
|
||||
this.documentVersion = 0
|
||||
this.changesTimeout = 0
|
||||
|
||||
this.client.attachPlugin(this)
|
||||
|
||||
this.initialize({
|
||||
documentText: this.view.state.doc.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
update({ docChanged }: ViewUpdate) {
|
||||
if (!docChanged) return
|
||||
if (this.changesTimeout) clearTimeout(this.changesTimeout)
|
||||
this.changesTimeout = self.setTimeout(() => {
|
||||
this.sendChange({
|
||||
documentText: this.view.state.doc.toString(),
|
||||
})
|
||||
}, changesDelay)
|
||||
}
|
||||
|
||||
async save(contents: string) {
|
||||
this.client.transportWriteFile(this.documentUri.split("file://")[1], contents)
|
||||
|
||||
await this.client.textDocumentDidSave({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
},
|
||||
text: contents,
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.client.detachPlugin(this)
|
||||
}
|
||||
|
||||
async initialize({ documentText }: { documentText: string }) {
|
||||
if (this.client.initializePromise) {
|
||||
await this.client.initializePromise
|
||||
}
|
||||
this.client.textDocumentDidOpen({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
languageId: this.languageId,
|
||||
text: documentText,
|
||||
version: this.documentVersion,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
if (!this.client.ready) return
|
||||
try {
|
||||
await this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: documentText }],
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
requestDiagnostics(view: EditorView) {
|
||||
this.sendChange({ documentText: view.state.doc.toString() })
|
||||
}
|
||||
|
||||
async requestHoverTooltip(
|
||||
view: EditorView,
|
||||
{ line, character }: { line: number, character: number }
|
||||
): Promise<Tooltip | null> {
|
||||
if (!this.client.ready || !this.client.capabilities.hoverProvider) return null
|
||||
|
||||
this.sendChange({ documentText: view.state.doc.toString() })
|
||||
const result = await this.client.textDocumentHover({
|
||||
textDocument: { uri: this.documentUri },
|
||||
position: { line, character },
|
||||
})
|
||||
if (!result) return null
|
||||
const { contents, range } = result
|
||||
let pos = posToOffset(view.state.doc, { line, character })
|
||||
let end: number
|
||||
if (range) {
|
||||
pos = posToOffset(view.state.doc, range.start)
|
||||
end = posToOffset(view.state.doc, range.end)
|
||||
}
|
||||
if (pos === null) return null
|
||||
const dom = document.createElement("div")
|
||||
dom.classList.add("documentation")
|
||||
dom.innerHTML = formatContents(contents).join("<br/>")
|
||||
return { pos, end, create: _view => ({ dom }), above: true }
|
||||
}
|
||||
|
||||
async requestCompletion(
|
||||
context: CompletionContext,
|
||||
{ line, character }: { line: number, character: number },
|
||||
{
|
||||
triggerKind,
|
||||
triggerCharacter,
|
||||
}: {
|
||||
triggerKind: CompletionTriggerKind
|
||||
triggerCharacter: string | undefined
|
||||
}
|
||||
): Promise<CompletionResult | null> {
|
||||
if (!this.client.ready || !this.client.capabilities.completionProvider) return null
|
||||
this.sendChange({
|
||||
documentText: context.state.doc.toString(),
|
||||
})
|
||||
|
||||
const result = await this.client.textDocumentCompletion({
|
||||
textDocument: { uri: this.documentUri },
|
||||
position: { line, character },
|
||||
context: {
|
||||
triggerKind,
|
||||
triggerCharacter,
|
||||
},
|
||||
})
|
||||
|
||||
if (!result) return null
|
||||
|
||||
const items = "items" in result ? result.items : result
|
||||
|
||||
let options = items.map(
|
||||
({
|
||||
detail,
|
||||
label,
|
||||
kind,
|
||||
textEdit,
|
||||
documentation,
|
||||
sortText,
|
||||
filterText,
|
||||
}) => {
|
||||
const completion: Completion & {
|
||||
filterText: string
|
||||
sortText?: string
|
||||
apply: string
|
||||
} = {
|
||||
label,
|
||||
detail,
|
||||
apply: textEdit?.newText ?? label,
|
||||
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
||||
sortText: sortText ?? label,
|
||||
filterText: filterText ?? label,
|
||||
}
|
||||
if (documentation) {
|
||||
completion.info = formatContents(documentation).join("\n")
|
||||
}
|
||||
return completion
|
||||
}
|
||||
)
|
||||
|
||||
const [, match] = prefixMatch(options)
|
||||
const token = context.matchBefore(match)
|
||||
let { pos } = context
|
||||
|
||||
if (token) {
|
||||
pos = token.from
|
||||
const word = token.text.toLowerCase()
|
||||
if (/^\w+$/.test(word)) {
|
||||
options = options
|
||||
.filter(({ filterText }) =>
|
||||
filterText.toLowerCase().startsWith(word)
|
||||
)
|
||||
.sort(({ apply: a }, { apply: b }) => {
|
||||
switch (true) {
|
||||
case a.startsWith(token.text) &&
|
||||
!b.startsWith(token.text):
|
||||
return -1
|
||||
case !a.startsWith(token.text) &&
|
||||
b.startsWith(token.text):
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
from: pos,
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
async requestFormat() {
|
||||
return await this.client.textDocumentFormatting({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
},
|
||||
// We need this to be protocol-compilant, but clangd *entirely* ignores it.
|
||||
// For control over formatting options, see .clang-format
|
||||
options: {
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
processNotification(notification: Notification) {
|
||||
try {
|
||||
switch (notification.method) {
|
||||
case "textDocument/publishDiagnostics":
|
||||
this.processDiagnostics(notification.params)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
processDiagnostics(params: LSP.PublishDiagnosticsParams) {
|
||||
if (params.uri !== this.documentUri) return
|
||||
|
||||
const diagnostics = params.diagnostics
|
||||
.map(({ range, message, severity }) => ({
|
||||
from: posToOffset(this.view.state.doc, range.start),
|
||||
to: posToOffset(this.view.state.doc, range.end),
|
||||
severity: ({
|
||||
[DiagnosticSeverity.Error]: "error",
|
||||
[DiagnosticSeverity.Warning]: "warning",
|
||||
[DiagnosticSeverity.Information]: "info",
|
||||
[DiagnosticSeverity.Hint]: "info",
|
||||
} as const)[severity],
|
||||
message,
|
||||
}))
|
||||
.filter(({ from, to }) => from !== null && to !== null && from !== undefined && to !== undefined)
|
||||
.sort((a, b) => {
|
||||
switch (true) {
|
||||
case a.from < b.from:
|
||||
return -1
|
||||
case a.from > b.from:
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
|
||||
}
|
||||
}
|
||||
|
||||
interface LanguageServerBaseOptions {
|
||||
rootUri: string | null
|
||||
workspaceFolders: LSP.WorkspaceFolder[] | null
|
||||
documentUri: string
|
||||
languageId: string
|
||||
}
|
||||
|
||||
interface LanguageServerClientOptions extends LanguageServerBaseOptions {
|
||||
transport: Transport
|
||||
autoClose?: boolean
|
||||
}
|
||||
|
||||
interface LanguageServerOptions extends LanguageServerClientOptions {
|
||||
client?: LanguageServerClient
|
||||
}
|
||||
|
||||
function languageServerWithTransport(options: LanguageServerOptions): [Extension[], (string) => Promise<void>]{
|
||||
let plugin: LanguageServerPlugin | null = null
|
||||
|
||||
const extension = [
|
||||
client.of(options.client || new LanguageServerClient({ ...options, autoClose: true })),
|
||||
documentUri.of(options.documentUri),
|
||||
languageId.of(options.languageId),
|
||||
ViewPlugin.define(view => (plugin = new LanguageServerPlugin(view))),
|
||||
hoverTooltip(
|
||||
(view, pos) =>
|
||||
plugin?.requestHoverTooltip(
|
||||
view,
|
||||
offsetToPos(view.state.doc, pos)
|
||||
) ?? null
|
||||
),
|
||||
autocompletion({
|
||||
override: [
|
||||
async context => {
|
||||
if (plugin == null) return null
|
||||
|
||||
const { state, pos, explicit } = context
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind =
|
||||
CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client.capabilities?.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}),
|
||||
keymap.of([
|
||||
{ key: "Alt-Shift-f", run: target => {
|
||||
(async () => {
|
||||
const formattingEdits = await plugin.requestFormat()
|
||||
if (!formattingEdits) return
|
||||
|
||||
const changes = formattingEdits.map(change => {
|
||||
return {
|
||||
from: posToOffset(target.state.doc, change.range.start),
|
||||
to: posToOffset(target.state.doc, change.range.end),
|
||||
insert: change.newText,
|
||||
}
|
||||
})
|
||||
|
||||
target.dispatch({ changes })
|
||||
})()
|
||||
|
||||
return true
|
||||
} },
|
||||
]),
|
||||
]
|
||||
|
||||
const saveDocument = async (contents: string) => {
|
||||
if (!plugin) return
|
||||
await plugin.save(contents)
|
||||
}
|
||||
|
||||
return [extension, saveDocument]
|
||||
}
|
||||
|
||||
function posToOffset(doc: Text, pos: { line: number, character: number }) {
|
||||
if (pos.line >= doc.lines) return
|
||||
const offset = doc.line(pos.line + 1).from + pos.character
|
||||
if (offset > doc.length) return
|
||||
return offset
|
||||
}
|
||||
|
||||
function offsetToPos(doc: Text, offset: number) {
|
||||
const line = doc.lineAt(offset)
|
||||
return {
|
||||
line: line.number - 1,
|
||||
character: offset - line.from,
|
||||
}
|
||||
}
|
||||
|
||||
function formatContents(
|
||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||
): string[] {
|
||||
if (Array.isArray(contents)) {
|
||||
return contents.flatMap(formatContentEntry)
|
||||
} else {
|
||||
return formatContentEntry(contents)
|
||||
}
|
||||
}
|
||||
|
||||
function formatContentEntry(entry: LSP.MarkupContent | LSP.MarkedString): string[] {
|
||||
if (typeof entry === "string") {
|
||||
return entry.split("\n")
|
||||
} else {
|
||||
return formatContentEntry(entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
function toSet(chars: Set<string>) {
|
||||
let preamble = ""
|
||||
let flat = Array.from(chars).join("")
|
||||
const words = /\w/.test(flat)
|
||||
if (words) {
|
||||
preamble += "\\w"
|
||||
flat = flat.replace(/\w/g, "")
|
||||
}
|
||||
return `[${preamble}${flat.replace(/[^\w\s]/g, "\\$&")}]`
|
||||
}
|
||||
|
||||
function prefixMatch(options: Completion[]) {
|
||||
const first = new Set<string>()
|
||||
const rest = new Set<string>()
|
||||
|
||||
for (const { apply } of options) {
|
||||
const applyString = apply as string
|
||||
const initial = applyString.at(0)
|
||||
const restStr = applyString.substring(1)
|
||||
|
||||
first.add(initial)
|
||||
for (const char of restStr) {
|
||||
rest.add(char)
|
||||
}
|
||||
}
|
||||
|
||||
const source = toSet(first) + toSet(rest) + "*$"
|
||||
return [new RegExp("^" + source), new RegExp(source)]
|
||||
}
|
||||
|
||||
export { LanguageServerClient, languageServerWithTransport }
|
||||
@@ -1,5 +1,3 @@
|
||||
import { mutate } from "swr"
|
||||
|
||||
import { ResponseError } from "./api"
|
||||
|
||||
const GITHUB_CLIENT_ID = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID
|
||||
@@ -9,20 +7,9 @@ export function isGitHubLoginSupported(): boolean {
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps
|
||||
export function showGitHubLoginWindow(popup: boolean, scope: string) {
|
||||
export function showGitHubLoginWindow(scope: string) {
|
||||
const url = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&scope=${encodeURIComponent(scope)}`
|
||||
const win = popup && window.open(url, "Sign in with GitHub", "popup,width=520,height=520,resizable,status")
|
||||
|
||||
if (win) {
|
||||
win.addEventListener("message", event => {
|
||||
if (event.data?.source === "decomp_me_login") {
|
||||
console.info("Got new user from popup", event.data.user)
|
||||
mutate("/user", event.data.user)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.location.href = url
|
||||
}
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
export async function requestMissingScopes<T>(makeRequest: () => Promise<T>): Promise<T> {
|
||||
@@ -33,7 +20,7 @@ export async function requestMissingScopes<T>(makeRequest: () => Promise<T>): Pr
|
||||
const scope = error.json.detail
|
||||
|
||||
console.warn("Missing scopes", scope)
|
||||
showGitHubLoginWindow(true, scope)
|
||||
showGitHubLoginWindow(scope)
|
||||
|
||||
throw new Error("Accept permissions and retry")
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@ const codeFontSize = createPersistedState("codeFontSize")
|
||||
const monospaceFont = createPersistedState("monospaceFont")
|
||||
const codeLineHeight = createPersistedState("codeLineHeight")
|
||||
const codeColorScheme = createPersistedState("codeColorScheme")
|
||||
const languageServerEnabled = createPersistedState("languageServerEnabled")
|
||||
|
||||
export const useTheme = () => theme("auto")
|
||||
export const useAutoRecompileSetting = () => autoRecompile(true)
|
||||
@@ -15,6 +16,7 @@ export const useCodeFontSize = () => codeFontSize(11)
|
||||
export const useMonospaceFont = () => monospaceFont(undefined)
|
||||
export const useCodeLineHeight = () => codeLineHeight(1.5)
|
||||
export const useCodeColorScheme = () => codeColorScheme("Frog Dark")
|
||||
export const useLanguageServerEnabled = () => languageServerEnabled(false)
|
||||
|
||||
export function useIsSiteThemeDark() {
|
||||
const [theme] = useTheme()
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function NewProjectPage() {
|
||||
disabled={!isSignedIn}
|
||||
>
|
||||
Create project
|
||||
</AsyncButton> : <GitHubLoginButton popup label="Sign in to create projects" />}
|
||||
</AsyncButton> : <GitHubLoginButton label="Sign in to create projects" />}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1013,6 +1013,13 @@
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@clangd-wasm/clangd-wasm@15.0.7-dev5":
|
||||
version "15.0.7-dev5"
|
||||
resolved "https://registry.yarnpkg.com/@clangd-wasm/clangd-wasm/-/clangd-wasm-15.0.7-dev5.tgz#6787f95ae28574c0f5addb6ca44177f0dd358a5c"
|
||||
integrity sha512-HwzZ0EgSayCgjgk9WRVjiurrqo2Tc7iJR1s1PDx718/kOSPgnc1JyIMH/0EazLDfB2pOPDewnxNwdCSDnDhz+w==
|
||||
dependencies:
|
||||
"@open-rpc/client-js" "^1.8.1"
|
||||
|
||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.6.0":
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.6.0.tgz#9c0ea57792b405a391599bd80acae19b8c4c6ff5"
|
||||
@@ -1330,6 +1337,16 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@open-rpc/client-js@^1.8.1":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@open-rpc/client-js/-/client-js-1.8.1.tgz#73b5a5bf237f24b14c3c89205b1fca3aea213213"
|
||||
integrity sha512-vV+Hetl688nY/oWI9IFY0iKDrWuLdYhf7OIKI6U1DcnJV7r4gAgwRJjEr1QVYszUc0gjkHoQJzqevmXMGLyA0g==
|
||||
dependencies:
|
||||
isomorphic-fetch "^3.0.0"
|
||||
isomorphic-ws "^5.0.0"
|
||||
strict-event-emitter-types "^2.0.0"
|
||||
ws "^7.0.0"
|
||||
|
||||
"@pkgr/utils@^2.3.1":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.0.tgz#b6373d2504aedaf2fc7cdf2d13ab1f48fa5f12d5"
|
||||
@@ -2113,6 +2130,16 @@ ast-types-flow@^0.0.7:
|
||||
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
|
||||
integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==
|
||||
|
||||
ast-types@0.9.6:
|
||||
version "0.9.6"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
||||
integrity sha512-qEdtR2UH78yyHX/AUNfXmJTlM48XoFZKBdwi1nzkI1mJL21cmbu0cvjxjpkXJ5NENMq42H+hNs8VLJcqXLerBQ==
|
||||
|
||||
ast-types@^0.9.2:
|
||||
version "0.9.14"
|
||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.14.tgz#d34ba5dffb9d15a44351fd2a9d82e4ab2838b5ba"
|
||||
integrity sha512-Ebvx7/0lLboCdyEmAw/4GqwBeKIijPveXNiVGhCGCNxc7z26T5he7DC6ARxu8ByKuzUZZcLog+VP8GMyZrBzJw==
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||
@@ -3264,6 +3291,11 @@ espree@^9.5.1:
|
||||
acorn-jsx "^5.3.2"
|
||||
eslint-visitor-keys "^3.4.0"
|
||||
|
||||
esprima@~3.1.0:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
||||
integrity sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==
|
||||
|
||||
esquery@^1.4.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
|
||||
@@ -4153,6 +4185,19 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
isomorphic-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
|
||||
integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==
|
||||
dependencies:
|
||||
node-fetch "^2.6.1"
|
||||
whatwg-fetch "^3.4.1"
|
||||
|
||||
isomorphic-ws@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
|
||||
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.8.5"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
|
||||
@@ -4238,7 +4283,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
json5@^1.0.2:
|
||||
json5@^1.0.1, json5@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
|
||||
@@ -4327,6 +4372,15 @@ loader-runner@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
|
||||
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
|
||||
|
||||
loader-utils@^1.0.3:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3"
|
||||
integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
|
||||
@@ -4670,6 +4724,13 @@ node-addon-api@^6.1.0:
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76"
|
||||
integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25"
|
||||
integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-releases@^2.0.8:
|
||||
version "2.0.10"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
|
||||
@@ -5325,6 +5386,11 @@ pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
||||
|
||||
private@~0.1.5:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
|
||||
|
||||
prop-types@^15.7.1, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
@@ -5494,6 +5560,16 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
recast@^0.11.17:
|
||||
version "0.11.23"
|
||||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
|
||||
integrity sha512-+nixG+3NugceyR8O1bLU45qs84JgI3+8EauyRZafLgC9XbdAOIVgwV1Pe2da0YzGo62KzWoZwUpVEQf6qNAXWA==
|
||||
dependencies:
|
||||
ast-types "0.9.6"
|
||||
esprima "~3.1.0"
|
||||
private "~0.1.5"
|
||||
source-map "~0.5.0"
|
||||
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
@@ -5858,6 +5934,11 @@ source-map@^0.8.0-beta.0:
|
||||
dependencies:
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
source-map@~0.5.0:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
@@ -5901,6 +5982,11 @@ streamsearch@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
strict-event-emitter-types@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f"
|
||||
integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==
|
||||
|
||||
string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
@@ -6372,6 +6458,11 @@ tr46@^1.0.1:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
trim-newlines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
@@ -6462,6 +6553,15 @@ typescript@^5.0.4:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
|
||||
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
|
||||
|
||||
umd-compat-loader@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/umd-compat-loader/-/umd-compat-loader-2.1.2.tgz#abf89be1591940a236cf8fa87f88d6d6f5a8da35"
|
||||
integrity sha512-RkTlsfrCxUISWqiTtYFFJank7b2Hhl4V2pc29nl0xOEGvvuVkpy1xnufhXfTituxgpW0HSrDk0JHlvPYZxEXKQ==
|
||||
dependencies:
|
||||
ast-types "^0.9.2"
|
||||
loader-utils "^1.0.3"
|
||||
recast "^0.11.17"
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
|
||||
@@ -6569,6 +6669,24 @@ validate-npm-package-license@^3.0.1:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
vscode-jsonrpc@8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94"
|
||||
integrity sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==
|
||||
|
||||
vscode-languageserver-protocol@^3.17.3:
|
||||
version "3.17.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz#6d0d54da093f0c0ee3060b81612cce0f11060d57"
|
||||
integrity sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==
|
||||
dependencies:
|
||||
vscode-jsonrpc "8.1.0"
|
||||
vscode-languageserver-types "3.17.3"
|
||||
|
||||
vscode-languageserver-types@3.17.3:
|
||||
version "3.17.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
|
||||
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
|
||||
|
||||
w3c-keyname@^2.2.4:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f"
|
||||
@@ -6589,6 +6707,11 @@ watchpack@^2.4.0:
|
||||
glob-to-regexp "^0.4.1"
|
||||
graceful-fs "^4.1.2"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
@@ -6652,6 +6775,19 @@ webpack@^5.81.0:
|
||||
watchpack "^2.4.0"
|
||||
webpack-sources "^3.2.3"
|
||||
|
||||
whatwg-fetch@^3.4.1:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
|
||||
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
whatwg-url@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||
@@ -6895,7 +7031,7 @@ write-file-atomic@^5.0.1:
|
||||
imurmurhash "^0.1.4"
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
ws@^7.3.1:
|
||||
ws@^7.0.0, ws@^7.3.1:
|
||||
version "7.5.9"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||
|
||||
Reference in New Issue
Block a user