lsp workspace stuff (#1677)
* some lsp shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * more stuffs Signed-off-by: Jess Frazelle <github@jessfraz.com> * on open send close and open events Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * update the path Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * send on close Signed-off-by: Jess Frazelle <github@jessfraz.com> * on close project Signed-off-by: Jess Frazelle <github@jessfraz.com> * update on close Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * initpromise Signed-off-by: Jess Frazelle <github@jessfraz.com> * add to wasm.ts Signed-off-by: Jess Frazelle <github@jessfraz.com> * Update src/lang/wasm.ts Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> * restart lsps on failure Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * add panic hook Signed-off-by: Jess Frazelle <github@jessfraz.com> * updartes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, MouseEventHandler } from 'react'
|
import { useCallback, MouseEventHandler, useEffect } from 'react'
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { PaneType, useStore } from './useStore'
|
import { PaneType, useStore } from './useStore'
|
||||||
@ -32,11 +32,17 @@ import { engineCommandManager } from './lang/std/engineConnection'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { useLspContext } from 'components/LspProvider'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { project, file } = useLoaderData() as IndexLoaderData
|
const { project, file } = useLoaderData() as IndexLoaderData
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
|
const { onProjectOpen } = useLspContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onProjectOpen(project || null, file || null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
|
@ -38,6 +38,7 @@ import { sep } from '@tauri-apps/api/path'
|
|||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
|
import LspProvider from 'components/LspProvider'
|
||||||
|
|
||||||
export const BROWSER_FILE_NAME = 'new'
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
@ -52,7 +53,9 @@ const addGlobalContextToElements = (
|
|||||||
...route,
|
...route,
|
||||||
element: (
|
element: (
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthProvider>{route.element}</SettingsAuthProvider>
|
<SettingsAuthProvider>
|
||||||
|
<LspProvider>{route.element}</LspProvider>
|
||||||
|
</SettingsAuthProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,20 @@ import { FILE_EXT, sortProject } from 'lib/tauriFS'
|
|||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { kclManager } from 'lang/KclSingleton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
||||||
|
import { useLspContext } from './LspProvider'
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an OS-agnostic way to get the basename of the path.
|
||||||
|
export function basename(path: string): string {
|
||||||
|
// Regular expression to match the last portion of the path, taking into account both POSIX and Windows delimiters
|
||||||
|
const re = /[^\\/]+$/
|
||||||
|
const match = path.match(re)
|
||||||
|
return match ? match[0] : ''
|
||||||
|
}
|
||||||
|
|
||||||
function RenameForm({
|
function RenameForm({
|
||||||
fileOrDir,
|
fileOrDir,
|
||||||
setIsRenaming,
|
setIsRenaming,
|
||||||
@ -147,6 +156,7 @@ const FileTreeItem = ({
|
|||||||
level?: number
|
level?: number
|
||||||
}) => {
|
}) => {
|
||||||
const { send, context } = useFileContext()
|
const { send, context } = useFileContext()
|
||||||
|
const { lspClients } = useLspContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [isRenaming, setIsRenaming] = useState(false)
|
const [isRenaming, setIsRenaming] = useState(false)
|
||||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
||||||
@ -174,6 +184,28 @@ const FileTreeItem = ({
|
|||||||
kclManager.code
|
kclManager.code
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
// Let the lsp servers know we closed a file.
|
||||||
|
const currentFilePath = basename(currentFile?.path || 'main.kcl')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidClose({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const newFilePath = basename(fileOrDir.path)
|
||||||
|
// Then let the clients know we opened a file.
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidOpen({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${newFilePath}`,
|
||||||
|
languageId: 'kcl',
|
||||||
|
version: 1,
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Open kcl files
|
// Open kcl files
|
||||||
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||||
}
|
}
|
||||||
|
191
src/components/LspProvider.tsx
Normal file
191
src/components/LspProvider.tsx
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
|
import React, { createContext, useMemo, useContext } from 'react'
|
||||||
|
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||||
|
import Server from '../editor/plugins/lsp/server'
|
||||||
|
import Client from '../editor/plugins/lsp/client'
|
||||||
|
import { TEST } from 'env'
|
||||||
|
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
||||||
|
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
|
import { Extension } from '@codemirror/state'
|
||||||
|
import { LanguageSupport } from '@codemirror/language'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { basename } from './FileTree'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
|
import { FileEntry } from '@tauri-apps/api/fs'
|
||||||
|
import { ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
|
|
||||||
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
type LspContext = {
|
||||||
|
lspClients: LanguageServerClient[]
|
||||||
|
copilotLSP: Extension | null
|
||||||
|
kclLSP: LanguageSupport | null
|
||||||
|
onProjectClose: (file: FileEntry | null, redirect: boolean) => void
|
||||||
|
onProjectOpen: (
|
||||||
|
project: ProjectWithEntryPointMetadata | null,
|
||||||
|
file: FileEntry | null
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LspStateContext = createContext({} as LspContext)
|
||||||
|
export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const {
|
||||||
|
isKclLspServerReady,
|
||||||
|
isCopilotLspServerReady,
|
||||||
|
setIsKclLspServerReady,
|
||||||
|
setIsCopilotLspServerReady,
|
||||||
|
} = useStore((s) => ({
|
||||||
|
isKclLspServerReady: s.isKclLspServerReady,
|
||||||
|
isCopilotLspServerReady: s.isCopilotLspServerReady,
|
||||||
|
setIsKclLspServerReady: s.setIsKclLspServerReady,
|
||||||
|
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { auth } = useSettingsAuthContext()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||||
|
// But the server happens async so we break this into two parts.
|
||||||
|
// Below is the client and server promise.
|
||||||
|
const { lspClient: kclLspClient } = useMemo(() => {
|
||||||
|
const intoServer: IntoServer = new IntoServer()
|
||||||
|
const fromServer: FromServer = FromServer.create()
|
||||||
|
const client = new Client(fromServer, intoServer)
|
||||||
|
if (!TEST) {
|
||||||
|
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
||||||
|
lspServer.start('kcl')
|
||||||
|
setIsKclLspServerReady(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const lspClient = new LanguageServerClient({ client, name: 'kcl' })
|
||||||
|
return { lspClient }
|
||||||
|
}, [setIsKclLspServerReady])
|
||||||
|
|
||||||
|
// Here we initialize the plugin which will start the client.
|
||||||
|
// Now that we have multi-file support the name of the file is a dep of
|
||||||
|
// this use memo, as well as the directory structure, which I think is
|
||||||
|
// a good setup because it will restart the client but not the server :)
|
||||||
|
// We do not want to restart the server, its just wasteful.
|
||||||
|
const kclLSP = useMemo(() => {
|
||||||
|
let plugin = null
|
||||||
|
if (isKclLspServerReady && !TEST) {
|
||||||
|
// Set up the lsp plugin.
|
||||||
|
const lsp = kclLanguage({
|
||||||
|
documentUri: `file:///main.kcl`,
|
||||||
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
|
client: kclLspClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
plugin = lsp
|
||||||
|
}
|
||||||
|
return plugin
|
||||||
|
}, [kclLspClient, isKclLspServerReady])
|
||||||
|
|
||||||
|
const { lspClient: copilotLspClient } = useMemo(() => {
|
||||||
|
const intoServer: IntoServer = new IntoServer()
|
||||||
|
const fromServer: FromServer = FromServer.create()
|
||||||
|
const client = new Client(fromServer, intoServer)
|
||||||
|
if (!TEST) {
|
||||||
|
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
||||||
|
const token = auth?.context?.token
|
||||||
|
lspServer.start('copilot', token)
|
||||||
|
setIsCopilotLspServerReady(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const lspClient = new LanguageServerClient({ client, name: 'copilot' })
|
||||||
|
return { lspClient }
|
||||||
|
}, [setIsCopilotLspServerReady])
|
||||||
|
|
||||||
|
// Here we initialize the plugin which will start the client.
|
||||||
|
// When we have multi-file support the name of the file will be a dep of
|
||||||
|
// this use memo, as well as the directory structure, which I think is
|
||||||
|
// a good setup because it will restart the client but not the server :)
|
||||||
|
// We do not want to restart the server, its just wasteful.
|
||||||
|
const copilotLSP = useMemo(() => {
|
||||||
|
let plugin = null
|
||||||
|
if (isCopilotLspServerReady && !TEST) {
|
||||||
|
// Set up the lsp plugin.
|
||||||
|
const lsp = copilotPlugin({
|
||||||
|
documentUri: `file:///main.kcl`,
|
||||||
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
|
client: copilotLspClient,
|
||||||
|
allowHTMLContent: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
plugin = lsp
|
||||||
|
}
|
||||||
|
return plugin
|
||||||
|
}, [copilotLspClient, isCopilotLspServerReady])
|
||||||
|
|
||||||
|
const lspClients = [kclLspClient, copilotLspClient]
|
||||||
|
|
||||||
|
const onProjectClose = (file: FileEntry | null, redirect: boolean) => {
|
||||||
|
const currentFilePath = basename(file?.name || 'main.kcl')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidClose({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
navigate(paths.HOME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onProjectOpen = (
|
||||||
|
project: ProjectWithEntryPointMetadata | null,
|
||||||
|
file: FileEntry | null
|
||||||
|
) => {
|
||||||
|
const projectName = project?.name || 'ProjectRoot'
|
||||||
|
// Send that the workspace folders changed.
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.workspaceDidChangeWorkspaceFolders(
|
||||||
|
[{ uri: 'file://', name: projectName }],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (file) {
|
||||||
|
// Send that the file was opened.
|
||||||
|
const filename = basename(file?.name || 'main.kcl')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidOpen({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${filename}`,
|
||||||
|
languageId: 'kcl',
|
||||||
|
version: 1,
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LspStateContext.Provider
|
||||||
|
value={{
|
||||||
|
lspClients,
|
||||||
|
copilotLSP,
|
||||||
|
kclLSP,
|
||||||
|
onProjectClose,
|
||||||
|
onProjectOpen,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</LspStateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LspProvider
|
||||||
|
|
||||||
|
export const useLspContext = () => {
|
||||||
|
return useContext(LspStateContext)
|
||||||
|
}
|
@ -12,6 +12,7 @@ import { Logo } from './Logo'
|
|||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { useLspContext } from './LspProvider'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -22,14 +23,24 @@ const ProjectSidebarMenu = ({
|
|||||||
project?: IndexLoaderData['project']
|
project?: IndexLoaderData['project']
|
||||||
file?: IndexLoaderData['file']
|
file?: IndexLoaderData['file']
|
||||||
}) => {
|
}) => {
|
||||||
|
const { onProjectClose } = useLspContext()
|
||||||
return (
|
return (
|
||||||
<div className="rounded-sm !no-underline h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center gap-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90">
|
<div className="rounded-sm !no-underline h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center gap-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90">
|
||||||
<Link to={paths.HOME} className="group">
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
onProjectClose(file || null, false)
|
||||||
|
}}
|
||||||
|
to={paths.HOME}
|
||||||
|
className="group"
|
||||||
|
>
|
||||||
<Logo className="w-auto h-5 text-chalkboard-120 dark:text-chalkboard-10 group-hover:text-energy-10" />
|
<Logo className="w-auto h-5 text-chalkboard-120 dark:text-chalkboard-10 group-hover:text-energy-10" />
|
||||||
</Link>
|
</Link>
|
||||||
{renderAsLink ? (
|
{renderAsLink ? (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
onProjectClose(file || null, false)
|
||||||
|
}}
|
||||||
to={paths.HOME}
|
to={paths.HOME}
|
||||||
className="!no-underline"
|
className="!no-underline"
|
||||||
data-testid="project-sidebar-link"
|
data-testid="project-sidebar-link"
|
||||||
@ -57,6 +68,7 @@ function ProjectMenuPopover({
|
|||||||
file?: IndexLoaderData['file']
|
file?: IndexLoaderData['file']
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
|
const { onProjectClose } = useLspContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
@ -149,8 +161,10 @@ function ProjectMenuPopover({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
{isTauri() && (
|
{isTauri() && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="button"
|
||||||
to={paths.HOME}
|
onClick={() => {
|
||||||
|
onProjectClose(file || null, true)
|
||||||
|
}}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faHome,
|
icon: faHome,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
|
@ -4,9 +4,6 @@ import ReactCodeMirror, {
|
|||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
keymap,
|
keymap,
|
||||||
} from '@uiw/react-codemirror'
|
} from '@uiw/react-codemirror'
|
||||||
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
|
||||||
import Server from '../editor/plugins/lsp/server'
|
|
||||||
import Client from '../editor/plugins/lsp/client'
|
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
@ -16,8 +13,6 @@ import { useEffect, useMemo, useRef } from 'react'
|
|||||||
import { linter, lintGutter } from '@codemirror/lint'
|
import { linter, lintGutter } from '@codemirror/lint'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { processCodeMirrorRanges } from 'lib/selections'
|
import { processCodeMirrorRanges } from 'lib/selections'
|
||||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
|
||||||
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
|
||||||
import { EditorView, lineHighlightField } from 'editor/highlightextension'
|
import { EditorView, lineHighlightField } from 'editor/highlightextension'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
@ -26,14 +21,11 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import interact from '@replit/codemirror-interact'
|
import interact from '@replit/codemirror-interact'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
import { useFileContext } from 'hooks/useFileContext'
|
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
|
||||||
import { isTauri } from 'lib/isTauri'
|
|
||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
|
||||||
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import { useLspContext } from './LspProvider'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -46,41 +38,21 @@ export const editorShortcutMeta = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
|
||||||
// We only use workspace folders in Tauri since that is where we use more than
|
|
||||||
// one file.
|
|
||||||
if (isTauri()) {
|
|
||||||
return [{ uri: 'file://', name: 'ProjectRoot' }]
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextEditor = ({
|
export const TextEditor = ({
|
||||||
theme,
|
theme,
|
||||||
}: {
|
}: {
|
||||||
theme: Themes.Light | Themes.Dark
|
theme: Themes.Light | Themes.Dark
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { editorView, setEditorView, isShiftDown } = useStore((s) => ({
|
||||||
editorView,
|
|
||||||
isKclLspServerReady,
|
|
||||||
isCopilotLspServerReady,
|
|
||||||
setEditorView,
|
|
||||||
setIsKclLspServerReady,
|
|
||||||
setIsCopilotLspServerReady,
|
|
||||||
isShiftDown,
|
|
||||||
} = useStore((s) => ({
|
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
isKclLspServerReady: s.isKclLspServerReady,
|
|
||||||
isCopilotLspServerReady: s.isCopilotLspServerReady,
|
|
||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
setIsKclLspServerReady: s.setIsKclLspServerReady,
|
|
||||||
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
|
|
||||||
isShiftDown: s.isShiftDown,
|
isShiftDown: s.isShiftDown,
|
||||||
}))
|
}))
|
||||||
const { code, errors } = useKclContext()
|
const { code, errors } = useKclContext()
|
||||||
const lastEvent = useRef({ event: '', time: Date.now() })
|
const lastEvent = useRef({ event: '', time: Date.now() })
|
||||||
const { overallState } = useNetworkStatus()
|
const { overallState } = useNetworkStatus()
|
||||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||||
|
const { copilotLSP, kclLSP } = useLspContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
@ -108,92 +80,12 @@ export const TextEditor = ({
|
|||||||
state,
|
state,
|
||||||
} = useModelingContext()
|
} = useModelingContext()
|
||||||
|
|
||||||
const { settings, auth } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const textWrapping = settings.context?.textWrapping ?? 'On'
|
const textWrapping = settings.context?.textWrapping ?? 'On'
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const {
|
|
||||||
context: { project },
|
|
||||||
} = useFileContext()
|
|
||||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
|
|
||||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
|
||||||
// But the server happens async so we break this into two parts.
|
|
||||||
// Below is the client and server promise.
|
|
||||||
const { lspClient: kclLspClient } = useMemo(() => {
|
|
||||||
const intoServer: IntoServer = new IntoServer()
|
|
||||||
const fromServer: FromServer = FromServer.create()
|
|
||||||
const client = new Client(fromServer, intoServer)
|
|
||||||
if (!TEST) {
|
|
||||||
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
|
||||||
lspServer.start('kcl')
|
|
||||||
setIsKclLspServerReady(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client, name: 'kcl' })
|
|
||||||
return { lspClient }
|
|
||||||
}, [setIsKclLspServerReady])
|
|
||||||
|
|
||||||
// Here we initialize the plugin which will start the client.
|
|
||||||
// Now that we have multi-file support the name of the file is a dep of
|
|
||||||
// this use memo, as well as the directory structure, which I think is
|
|
||||||
// a good setup because it will restart the client but not the server :)
|
|
||||||
// We do not want to restart the server, its just wasteful.
|
|
||||||
const kclLSP = useMemo(() => {
|
|
||||||
let plugin = null
|
|
||||||
if (isKclLspServerReady && !TEST) {
|
|
||||||
// Set up the lsp plugin.
|
|
||||||
const lsp = kclLanguage({
|
|
||||||
// When we have more than one file, we'll need to change this.
|
|
||||||
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
|
||||||
client: kclLspClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
plugin = lsp
|
|
||||||
}
|
|
||||||
return plugin
|
|
||||||
}, [kclLspClient, isKclLspServerReady])
|
|
||||||
|
|
||||||
const { lspClient: copilotLspClient } = useMemo(() => {
|
|
||||||
const intoServer: IntoServer = new IntoServer()
|
|
||||||
const fromServer: FromServer = FromServer.create()
|
|
||||||
const client = new Client(fromServer, intoServer)
|
|
||||||
if (!TEST) {
|
|
||||||
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
|
||||||
const token = auth?.context?.token
|
|
||||||
lspServer.start('copilot', token)
|
|
||||||
setIsCopilotLspServerReady(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client, name: 'copilot' })
|
|
||||||
return { lspClient }
|
|
||||||
}, [setIsCopilotLspServerReady])
|
|
||||||
|
|
||||||
// Here we initialize the plugin which will start the client.
|
|
||||||
// When we have multi-file support the name of the file will be a dep of
|
|
||||||
// this use memo, as well as the directory structure, which I think is
|
|
||||||
// a good setup because it will restart the client but not the server :)
|
|
||||||
// We do not want to restart the server, its just wasteful.
|
|
||||||
const copilotLSP = useMemo(() => {
|
|
||||||
let plugin = null
|
|
||||||
if (isCopilotLspServerReady && !TEST) {
|
|
||||||
// Set up the lsp plugin.
|
|
||||||
const lsp = copilotPlugin({
|
|
||||||
// When we have more than one file, we'll need to change this.
|
|
||||||
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
|
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
|
||||||
client: copilotLspClient,
|
|
||||||
allowHTMLContent: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
plugin = lsp
|
|
||||||
}
|
|
||||||
return plugin
|
|
||||||
}, [copilotLspClient, isCopilotLspServerReady, project])
|
|
||||||
|
|
||||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||||
const onChange = async (newCode: string) => {
|
const onChange = async (newCode: string) => {
|
||||||
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
||||||
|
@ -88,6 +88,8 @@ interface LSPNotifyMap {
|
|||||||
initialized: LSP.InitializedParams
|
initialized: LSP.InitializedParams
|
||||||
'textDocument/didChange': LSP.DidChangeTextDocumentParams
|
'textDocument/didChange': LSP.DidChangeTextDocumentParams
|
||||||
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
||||||
|
'textDocument/didClose': LSP.DidCloseTextDocumentParams
|
||||||
|
'workspace/didChangeWorkspaceFolders': LSP.DidChangeWorkspaceFoldersParams
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LanguageServerClientOptions {
|
export interface LanguageServerClientOptions {
|
||||||
@ -149,6 +151,12 @@ export class LanguageServerClient {
|
|||||||
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
||||||
this.notify('textDocument/didOpen', params)
|
this.notify('textDocument/didOpen', params)
|
||||||
|
|
||||||
|
// Update the facet of the plugins to the correct value.
|
||||||
|
for (const plugin of this.plugins) {
|
||||||
|
plugin.documentUri = params.textDocument.uri
|
||||||
|
plugin.languageId = params.textDocument.languageId
|
||||||
|
}
|
||||||
|
|
||||||
this.updateSemanticTokens(params.textDocument.uri)
|
this.updateSemanticTokens(params.textDocument.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +165,28 @@ export class LanguageServerClient {
|
|||||||
this.updateSemanticTokens(params.textDocument.uri)
|
this.updateSemanticTokens(params.textDocument.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textDocumentDidClose(params: LSP.DidCloseTextDocumentParams) {
|
||||||
|
this.notify('textDocument/didClose', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceDidChangeWorkspaceFolders(
|
||||||
|
added: LSP.WorkspaceFolder[],
|
||||||
|
removed: LSP.WorkspaceFolder[]
|
||||||
|
) {
|
||||||
|
// Add all the current workspace folders in the plugin to removed.
|
||||||
|
for (const plugin of this.plugins) {
|
||||||
|
removed.push(...plugin.workspaceFolders)
|
||||||
|
}
|
||||||
|
this.notify('workspace/didChangeWorkspaceFolders', {
|
||||||
|
event: { added, removed },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add all the new workspace folders to the plugins.
|
||||||
|
for (const plugin of this.plugins) {
|
||||||
|
plugin.workspaceFolders = added
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateSemanticTokens(uri: string) {
|
async updateSemanticTokens(uri: string) {
|
||||||
const serverCapabilities = this.getServerCapabilities()
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
if (!serverCapabilities.semanticTokensProvider) {
|
if (!serverCapabilities.semanticTokensProvider) {
|
||||||
|
@ -37,9 +37,9 @@ const CompletionItemKindMap = Object.fromEntries(
|
|||||||
|
|
||||||
export class LanguageServerPlugin implements PluginValue {
|
export class LanguageServerPlugin implements PluginValue {
|
||||||
public client: LanguageServerClient
|
public client: LanguageServerClient
|
||||||
private documentUri: string
|
public documentUri: string
|
||||||
private languageId: string
|
public languageId: string
|
||||||
private workspaceFolders: LSP.WorkspaceFolder[]
|
public workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import init, {
|
import { InitOutput, ServerConfig } from 'wasm-lib/pkg/wasm_lib'
|
||||||
copilot_lsp_run,
|
|
||||||
InitOutput,
|
|
||||||
kcl_lsp_run,
|
|
||||||
ServerConfig,
|
|
||||||
} from 'wasm-lib/pkg/wasm_lib'
|
|
||||||
import { FromServer, IntoServer } from './codec'
|
import { FromServer, IntoServer } from './codec'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
|
import { copilotLspRun, initPromise, kclLspRun } from 'lang/wasm'
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
readonly initOutput: InitOutput
|
readonly initOutput: InitOutput
|
||||||
@ -26,7 +22,7 @@ export default class Server {
|
|||||||
intoServer: IntoServer,
|
intoServer: IntoServer,
|
||||||
fromServer: FromServer
|
fromServer: FromServer
|
||||||
): Promise<Server> {
|
): Promise<Server> {
|
||||||
const initOutput = await init()
|
const initOutput = await initPromise
|
||||||
const server = new Server(initOutput, intoServer, fromServer)
|
const server = new Server(initOutput, intoServer, fromServer)
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
@ -38,12 +34,12 @@ export default class Server {
|
|||||||
fileSystemManager
|
fileSystemManager
|
||||||
)
|
)
|
||||||
if (type_ === 'copilot') {
|
if (type_ === 'copilot') {
|
||||||
if (!token) {
|
if (!token || token === '') {
|
||||||
throw new Error('auth token is required for copilot')
|
throw new Error('auth token is required for copilot')
|
||||||
}
|
}
|
||||||
await copilot_lsp_run(config, token)
|
await copilotLspRun(config, token)
|
||||||
} else if (type_ === 'kcl') {
|
} else if (type_ === 'kcl') {
|
||||||
await kcl_lsp_run(config)
|
await kclLspRun(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1446,6 +1446,9 @@ export class EngineCommandManager {
|
|||||||
if (this.engineConnection === undefined) {
|
if (this.engineConnection === undefined) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
if (!this.engineConnection?.isReady()) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
throw new Error('id is undefined')
|
throw new Error('id is undefined')
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ import init, {
|
|||||||
is_points_ccw,
|
is_points_ccw,
|
||||||
get_tangential_arc_to_info,
|
get_tangential_arc_to_info,
|
||||||
program_memory_init,
|
program_memory_init,
|
||||||
|
ServerConfig,
|
||||||
|
copilot_lsp_run,
|
||||||
|
kcl_lsp_run,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
@ -279,3 +282,27 @@ export function programMemoryInit(): ProgramMemory {
|
|||||||
throw kclError
|
throw kclError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function copilotLspRun(config: ServerConfig, token: string) {
|
||||||
|
try {
|
||||||
|
console.log('starting copilot lsp')
|
||||||
|
await copilot_lsp_run(config, token)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log('copilot lsp failed', e)
|
||||||
|
// We make it restart recursively so that if it ever dies after like
|
||||||
|
// 8 hours or something it will come back to life.
|
||||||
|
await copilotLspRun(config, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function kclLspRun(config: ServerConfig) {
|
||||||
|
try {
|
||||||
|
console.log('start kcl lsp')
|
||||||
|
await kcl_lsp_run(config)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log('kcl lsp failed', e)
|
||||||
|
// We make it restart recursively so that if it ever dies after like
|
||||||
|
// 8 hours or something it will come back to life.
|
||||||
|
await kclLspRun(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
11
src/wasm-lib/Cargo.lock
generated
11
src/wasm-lib/Cargo.lock
generated
@ -651,6 +651,16 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
@ -4869,6 +4879,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bson",
|
"bson",
|
||||||
|
"console_error_panic_hook",
|
||||||
"futures",
|
"futures",
|
||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
"image",
|
"image",
|
||||||
|
@ -30,6 +30,7 @@ twenty-twenty = "0.7"
|
|||||||
uuid = { version = "1.7.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.7.0", features = ["v4", "js", "serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
console_error_panic_hook = "0.1.7"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
js-sys = "0.3.69"
|
js-sys = "0.3.69"
|
||||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||||
|
@ -5,7 +5,7 @@ use tower_lsp::lsp_types::{
|
|||||||
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
||||||
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
|
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
|
||||||
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializedParams, MessageType, RenameFilesParams,
|
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializedParams, MessageType, RenameFilesParams,
|
||||||
TextDocumentItem,
|
TextDocumentItem, WorkspaceFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for the backend of the language server.
|
/// A trait for the backend of the language server.
|
||||||
@ -15,12 +15,21 @@ pub trait Backend {
|
|||||||
|
|
||||||
fn fs(&self) -> crate::fs::FileManager;
|
fn fs(&self) -> crate::fs::FileManager;
|
||||||
|
|
||||||
|
fn workspace_folders(&self) -> Vec<WorkspaceFolder>;
|
||||||
|
|
||||||
|
fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>);
|
||||||
|
|
||||||
|
fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>);
|
||||||
|
|
||||||
/// Get the current code map.
|
/// Get the current code map.
|
||||||
fn current_code_map(&self) -> DashMap<String, String>;
|
fn current_code_map(&self) -> DashMap<String, String>;
|
||||||
|
|
||||||
/// Insert a new code map.
|
/// Insert a new code map.
|
||||||
fn insert_current_code_map(&self, uri: String, text: String);
|
fn insert_current_code_map(&self, uri: String, text: String);
|
||||||
|
|
||||||
|
/// Clear the current code state.
|
||||||
|
fn clear_code_state(&self);
|
||||||
|
|
||||||
/// On change event.
|
/// On change event.
|
||||||
async fn on_change(&self, params: TextDocumentItem);
|
async fn on_change(&self, params: TextDocumentItem);
|
||||||
|
|
||||||
@ -43,9 +52,11 @@ pub trait Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
|
async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
|
||||||
self.client()
|
self.add_workspace_folders(params.event.added);
|
||||||
.log_message(MessageType::INFO, format!("workspace folders changed: {:?}", params))
|
self.remove_workspace_folders(params.event.removed);
|
||||||
.await;
|
// Remove the code from the current code map.
|
||||||
|
// We do this since it means the user is changing projects so let's refresh the state.
|
||||||
|
self.clear_code_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) {
|
async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) {
|
||||||
@ -117,5 +128,14 @@ pub trait Backend {
|
|||||||
self.client()
|
self.client()
|
||||||
.log_message(MessageType::INFO, format!("document closed: {:?}", params))
|
.log_message(MessageType::INFO, format!("document closed: {:?}", params))
|
||||||
.await;
|
.await;
|
||||||
|
self.client()
|
||||||
|
.log_message(MessageType::INFO, format!("uri: {:?}", params.text_document.uri))
|
||||||
|
.await;
|
||||||
|
// Get the workspace folders.
|
||||||
|
// The key of the workspace folder is the project name.
|
||||||
|
let workspace_folders = self.workspace_folders();
|
||||||
|
self.client()
|
||||||
|
.log_message(MessageType::INFO, format!("workspace: {:?}", workspace_folders))
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ use tower_lsp::{
|
|||||||
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
|
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
|
||||||
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams,
|
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams,
|
||||||
MessageType, OneOf, RenameFilesParams, ServerCapabilities, TextDocumentItem, TextDocumentSyncCapability,
|
MessageType, OneOf, RenameFilesParams, ServerCapabilities, TextDocumentItem, TextDocumentSyncCapability,
|
||||||
TextDocumentSyncKind, TextDocumentSyncOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
|
TextDocumentSyncKind, TextDocumentSyncOptions, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
|
||||||
|
WorkspaceServerCapabilities,
|
||||||
},
|
},
|
||||||
LanguageServer,
|
LanguageServer,
|
||||||
};
|
};
|
||||||
@ -44,6 +45,8 @@ pub struct Backend {
|
|||||||
pub client: tower_lsp::Client,
|
pub client: tower_lsp::Client,
|
||||||
/// The file system client to use.
|
/// The file system client to use.
|
||||||
pub fs: crate::fs::FileManager,
|
pub fs: crate::fs::FileManager,
|
||||||
|
/// The workspace folders.
|
||||||
|
pub workspace_folders: DashMap<String, WorkspaceFolder>,
|
||||||
/// Current code.
|
/// Current code.
|
||||||
pub current_code_map: DashMap<String, String>,
|
pub current_code_map: DashMap<String, String>,
|
||||||
/// The token is used to authenticate requests to the API server.
|
/// The token is used to authenticate requests to the API server.
|
||||||
@ -65,6 +68,22 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
self.fs.clone()
|
self.fs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
|
||||||
|
self.workspace_folders.iter().map(|v| v.value().clone()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
|
||||||
|
for folder in folders {
|
||||||
|
self.workspace_folders.insert(folder.name.to_string(), folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
|
||||||
|
for folder in folders {
|
||||||
|
self.workspace_folders.remove(&folder.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, String> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
||||||
@ -73,6 +92,10 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
self.current_code_map.insert(uri, text);
|
self.current_code_map.insert(uri, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_code_state(&self) {
|
||||||
|
self.current_code_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
async fn on_change(&self, _params: TextDocumentItem) {
|
async fn on_change(&self, _params: TextDocumentItem) {
|
||||||
// We don't need to do anything here.
|
// We don't need to do anything here.
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use tower_lsp::{
|
|||||||
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
|
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
|
||||||
StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
|
StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
|
||||||
TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
|
TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
|
||||||
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
|
WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
|
||||||
},
|
},
|
||||||
Client, LanguageServer,
|
Client, LanguageServer,
|
||||||
};
|
};
|
||||||
@ -49,6 +49,8 @@ pub struct Backend {
|
|||||||
pub client: Client,
|
pub client: Client,
|
||||||
/// The file system client to use.
|
/// The file system client to use.
|
||||||
pub fs: crate::fs::FileManager,
|
pub fs: crate::fs::FileManager,
|
||||||
|
/// The workspace folders.
|
||||||
|
pub workspace_folders: DashMap<String, WorkspaceFolder>,
|
||||||
/// The stdlib completions for the language.
|
/// The stdlib completions for the language.
|
||||||
pub stdlib_completions: HashMap<String, CompletionItem>,
|
pub stdlib_completions: HashMap<String, CompletionItem>,
|
||||||
/// The stdlib signatures for the language.
|
/// The stdlib signatures for the language.
|
||||||
@ -80,6 +82,22 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
self.fs.clone()
|
self.fs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
|
||||||
|
self.workspace_folders.iter().map(|v| v.value().clone()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
|
||||||
|
for folder in folders {
|
||||||
|
self.workspace_folders.insert(folder.name.to_string(), folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
|
||||||
|
for folder in folders {
|
||||||
|
self.workspace_folders.remove(&folder.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, String> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
||||||
@ -88,6 +106,15 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
self.current_code_map.insert(uri, text);
|
self.current_code_map.insert(uri, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_code_state(&self) {
|
||||||
|
self.current_code_map.clear();
|
||||||
|
self.token_map.clear();
|
||||||
|
self.ast_map.clear();
|
||||||
|
self.diagnostics_map.clear();
|
||||||
|
self.symbols_map.clear();
|
||||||
|
self.semantic_tokens_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
async fn on_change(&self, params: TextDocumentItem) {
|
async fn on_change(&self, params: TextDocumentItem) {
|
||||||
// We already updated the code map in the shared backend.
|
// We already updated the code map in the shared backend.
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ pub async fn execute_wasm(
|
|||||||
engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||||
fs_manager: kcl_lib::fs::wasm::FileSystemManager,
|
fs_manager: kcl_lib::fs::wasm::FileSystemManager,
|
||||||
) -> Result<JsValue, String> {
|
) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
|
|
||||||
use kcl_lib::executor::ExecutorContext;
|
use kcl_lib::executor::ExecutorContext;
|
||||||
@ -54,6 +55,8 @@ pub async fn modify_ast_for_sketch_wasm(
|
|||||||
plane_type: &str,
|
plane_type: &str,
|
||||||
sketch_id: &str,
|
sketch_id: &str,
|
||||||
) -> Result<JsValue, String> {
|
) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
let mut program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
let mut program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
@ -80,6 +83,8 @@ pub async fn modify_ast_for_sketch_wasm(
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
||||||
|
|
||||||
if let Some(success) = ws_resp.success {
|
if let Some(success) = ws_resp.success {
|
||||||
@ -99,12 +104,16 @@ pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
|||||||
// test for this function and by extension lexer are done in javascript land src/lang/tokeniser.test.ts
|
// test for this function and by extension lexer are done in javascript land src/lang/tokeniser.test.ts
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn lexer_wasm(js: &str) -> Result<JsValue, JsError> {
|
pub fn lexer_wasm(js: &str) -> Result<JsValue, JsError> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let tokens = kcl_lib::token::lexer(js);
|
let tokens = kcl_lib::token::lexer(js);
|
||||||
Ok(JsValue::from_serde(&tokens)?)
|
Ok(JsValue::from_serde(&tokens)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn parse_wasm(js: &str) -> Result<JsValue, String> {
|
pub fn parse_wasm(js: &str) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let tokens = kcl_lib::token::lexer(js);
|
let tokens = kcl_lib::token::lexer(js);
|
||||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||||
let program = parser.ast().map_err(String::from)?;
|
let program = parser.ast().map_err(String::from)?;
|
||||||
@ -117,6 +126,8 @@ pub fn parse_wasm(js: &str) -> Result<JsValue, String> {
|
|||||||
// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts
|
// test for this function and by extension the recaster are done in javascript land src/lang/recast.test.ts
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
let program: kcl_lib::ast::types::Program = serde_json::from_str(json_str).map_err(JsError::from)?;
|
let program: kcl_lib::ast::types::Program = serde_json::from_str(json_str).map_err(JsError::from)?;
|
||||||
|
|
||||||
@ -157,6 +168,8 @@ impl ServerConfig {
|
|||||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
into_server,
|
into_server,
|
||||||
from_server,
|
from_server,
|
||||||
@ -173,6 +186,7 @@ pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
|||||||
let (service, socket) = LspService::new(|client| kcl_lib::lsp::kcl::Backend {
|
let (service, socket) = LspService::new(|client| kcl_lib::lsp::kcl::Backend {
|
||||||
client,
|
client,
|
||||||
fs: kcl_lib::fs::FileManager::new(fs),
|
fs: kcl_lib::fs::FileManager::new(fs),
|
||||||
|
workspace_folders: Default::default(),
|
||||||
stdlib_completions,
|
stdlib_completions,
|
||||||
stdlib_signatures,
|
stdlib_signatures,
|
||||||
token_types,
|
token_types,
|
||||||
@ -213,6 +227,8 @@ pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
|||||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(), JsValue> {
|
pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(), JsValue> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
into_server,
|
into_server,
|
||||||
from_server,
|
from_server,
|
||||||
@ -222,6 +238,7 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(),
|
|||||||
let (service, socket) = LspService::build(|client| kcl_lib::lsp::copilot::Backend {
|
let (service, socket) = LspService::build(|client| kcl_lib::lsp::copilot::Backend {
|
||||||
client,
|
client,
|
||||||
fs: kcl_lib::fs::FileManager::new(fs),
|
fs: kcl_lib::fs::FileManager::new(fs),
|
||||||
|
workspace_folders: Default::default(),
|
||||||
current_code_map: Default::default(),
|
current_code_map: Default::default(),
|
||||||
editor_info: Arc::new(RwLock::new(kcl_lib::lsp::copilot::types::CopilotEditorInfo::default())),
|
editor_info: Arc::new(RwLock::new(kcl_lib::lsp::copilot::types::CopilotEditorInfo::default())),
|
||||||
cache: kcl_lib::lsp::copilot::cache::CopilotCache::new(),
|
cache: kcl_lib::lsp::copilot::cache::CopilotCache::new(),
|
||||||
@ -258,6 +275,8 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(),
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn is_points_ccw(points: &[f64]) -> i32 {
|
pub fn is_points_ccw(points: &[f64]) -> i32 {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
kcl_lib::std::utils::is_points_ccw_wasm(points)
|
kcl_lib::std::utils::is_points_ccw_wasm(points)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,11 +310,13 @@ pub fn get_tangential_arc_to_info(
|
|||||||
tan_previous_point_y: f64,
|
tan_previous_point_y: f64,
|
||||||
obtuse: bool,
|
obtuse: bool,
|
||||||
) -> TangentialArcInfoOutputWasm {
|
) -> TangentialArcInfoOutputWasm {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let result = kcl_lib::std::utils::get_tangential_arc_to_info(kcl_lib::std::utils::TangentialArcInfoInput {
|
let result = kcl_lib::std::utils::get_tangential_arc_to_info(kcl_lib::std::utils::TangentialArcInfoInput {
|
||||||
arc_start_point: [arc_start_point_x, arc_start_point_y],
|
arc_start_point: [arc_start_point_x, arc_start_point_y],
|
||||||
arc_end_point: [arc_end_point_x, arc_end_point_y],
|
arc_end_point: [arc_end_point_x, arc_end_point_y],
|
||||||
tan_previous_point: [tan_previous_point_x, tan_previous_point_y],
|
tan_previous_point: [tan_previous_point_x, tan_previous_point_y],
|
||||||
obtuse: obtuse,
|
obtuse,
|
||||||
});
|
});
|
||||||
TangentialArcInfoOutputWasm {
|
TangentialArcInfoOutputWasm {
|
||||||
center_x: result.center[0],
|
center_x: result.center[0],
|
||||||
@ -312,6 +333,8 @@ pub fn get_tangential_arc_to_info(
|
|||||||
/// Create the default program memory.
|
/// Create the default program memory.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn program_memory_init() -> Result<JsValue, String> {
|
pub fn program_memory_init() -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let memory = kcl_lib::executor::ProgramMemory::default();
|
let memory = kcl_lib::executor::ProgramMemory::default();
|
||||||
|
|
||||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||||
|
Reference in New Issue
Block a user