More lsp stuff / telemetry-prep (#1694)
* more text document stuff Signed-off-by: Jess Frazelle <github@jessfraz.com> * backend for rename and create etc Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates for functions Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * js future Signed-off-by: Jess Frazelle <github@jessfraz.com> * utils Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup send and sync shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * save the client Signed-off-by: Jess Frazelle <github@jessfraz.com> * store the users privacy settings 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> * bump version Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -21,14 +21,6 @@ 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,
|
||||||
@ -156,7 +148,7 @@ const FileTreeItem = ({
|
|||||||
level?: number
|
level?: number
|
||||||
}) => {
|
}) => {
|
||||||
const { send, context } = useFileContext()
|
const { send, context } = useFileContext()
|
||||||
const { lspClients } = useLspContext()
|
const { onFileOpen, onFileClose } = 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)
|
||||||
@ -185,26 +177,8 @@ const FileTreeItem = ({
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Let the lsp servers know we closed a file.
|
// Let the lsp servers know we closed a file.
|
||||||
const currentFilePath = basename(currentFile?.path || 'main.kcl')
|
onFileClose(currentFile?.path || null, project?.path || null)
|
||||||
lspClients.forEach((lspClient) => {
|
onFileOpen(fileOrDir.path, project?.path || null)
|
||||||
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)}`)
|
||||||
|
@ -12,24 +12,46 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|||||||
import { Extension } from '@codemirror/state'
|
import { Extension } from '@codemirror/state'
|
||||||
import { LanguageSupport } from '@codemirror/language'
|
import { LanguageSupport } from '@codemirror/language'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { basename } from './FileTree'
|
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { FileEntry } from '@tauri-apps/api/fs'
|
import { FileEntry } from '@tauri-apps/api/fs'
|
||||||
import { ProjectWithEntryPointMetadata } from 'lib/types'
|
import { ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
|
|
||||||
|
const DEFAULT_FILE_NAME: string = 'main.kcl'
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an OS-agnostic way to get the basename of the path.
|
||||||
|
export function projectBasename(filePath: string, projectPath: string): string {
|
||||||
|
const newPath = filePath.replace(projectPath, '')
|
||||||
|
// Trim any leading slashes.
|
||||||
|
let trimmedStr = newPath.replace(/^\/+/, '').replace(/^\\+/, '')
|
||||||
|
return trimmedStr
|
||||||
|
}
|
||||||
|
|
||||||
type LspContext = {
|
type LspContext = {
|
||||||
lspClients: LanguageServerClient[]
|
lspClients: LanguageServerClient[]
|
||||||
copilotLSP: Extension | null
|
copilotLSP: Extension | null
|
||||||
kclLSP: LanguageSupport | null
|
kclLSP: LanguageSupport | null
|
||||||
onProjectClose: (file: FileEntry | null, redirect: boolean) => void
|
onProjectClose: (
|
||||||
|
file: FileEntry | null,
|
||||||
|
projectPath: string | null,
|
||||||
|
redirect: boolean
|
||||||
|
) => void
|
||||||
onProjectOpen: (
|
onProjectOpen: (
|
||||||
project: ProjectWithEntryPointMetadata | null,
|
project: ProjectWithEntryPointMetadata | null,
|
||||||
file: FileEntry | null
|
file: FileEntry | null
|
||||||
) => void
|
) => void
|
||||||
|
onFileOpen: (filePath: string | null, projectPath: string | null) => void
|
||||||
|
onFileClose: (filePath: string | null, projectPath: string | null) => void
|
||||||
|
onFileCreate: (file: FileEntry, projectPath: string | null) => void
|
||||||
|
onFileRename: (
|
||||||
|
oldFile: FileEntry,
|
||||||
|
newFile: FileEntry,
|
||||||
|
projectPath: string | null
|
||||||
|
) => void
|
||||||
|
onFileDelete: (file: FileEntry, projectPath: string | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LspStateContext = createContext({} as LspContext)
|
export const LspStateContext = createContext({} as LspContext)
|
||||||
@ -58,7 +80,8 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const client = new Client(fromServer, intoServer)
|
const client = new Client(fromServer, intoServer)
|
||||||
if (!TEST) {
|
if (!TEST) {
|
||||||
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
||||||
lspServer.start('kcl')
|
const token = auth?.context?.token
|
||||||
|
lspServer.start('kcl', token)
|
||||||
setIsKclLspServerReady(true)
|
setIsKclLspServerReady(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -77,7 +100,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (isKclLspServerReady && !TEST) {
|
if (isKclLspServerReady && !TEST) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = kclLanguage({
|
const lsp = kclLanguage({
|
||||||
documentUri: `file:///main.kcl`,
|
documentUri: `file:///${DEFAULT_FILE_NAME}`,
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: kclLspClient,
|
client: kclLspClient,
|
||||||
})
|
})
|
||||||
@ -113,7 +136,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (isCopilotLspServerReady && !TEST) {
|
if (isCopilotLspServerReady && !TEST) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = copilotPlugin({
|
const lsp = copilotPlugin({
|
||||||
documentUri: `file:///main.kcl`,
|
documentUri: `file:///${DEFAULT_FILE_NAME}`,
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: copilotLspClient,
|
client: copilotLspClient,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
@ -126,8 +149,15 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
const lspClients = [kclLspClient, copilotLspClient]
|
const lspClients = [kclLspClient, copilotLspClient]
|
||||||
|
|
||||||
const onProjectClose = (file: FileEntry | null, redirect: boolean) => {
|
const onProjectClose = (
|
||||||
const currentFilePath = basename(file?.name || 'main.kcl')
|
file: FileEntry | null,
|
||||||
|
projectPath: string | null,
|
||||||
|
redirect: boolean
|
||||||
|
) => {
|
||||||
|
const currentFilePath = projectBasename(
|
||||||
|
file?.path || DEFAULT_FILE_NAME,
|
||||||
|
projectPath || ''
|
||||||
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
lspClient.textDocumentDidClose({
|
lspClient.textDocumentDidClose({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
@ -155,7 +185,10 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
})
|
})
|
||||||
if (file) {
|
if (file) {
|
||||||
// Send that the file was opened.
|
// Send that the file was opened.
|
||||||
const filename = basename(file?.name || 'main.kcl')
|
const filename = projectBasename(
|
||||||
|
file?.path || DEFAULT_FILE_NAME,
|
||||||
|
project?.path || ''
|
||||||
|
)
|
||||||
lspClients.forEach((lspClient) => {
|
lspClients.forEach((lspClient) => {
|
||||||
lspClient.textDocumentDidOpen({
|
lspClient.textDocumentDidOpen({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
@ -169,6 +202,82 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onFileOpen = (filePath: string | null, projectPath: string | null) => {
|
||||||
|
const currentFilePath = projectBasename(
|
||||||
|
filePath || DEFAULT_FILE_NAME,
|
||||||
|
projectPath || ''
|
||||||
|
)
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidOpen({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
languageId: 'kcl',
|
||||||
|
version: 1,
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFileClose = (filePath: string | null, projectPath: string | null) => {
|
||||||
|
const currentFilePath = projectBasename(
|
||||||
|
filePath || DEFAULT_FILE_NAME,
|
||||||
|
projectPath || ''
|
||||||
|
)
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.textDocumentDidClose({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFileCreate = (file: FileEntry, projectPath: string | null) => {
|
||||||
|
const currentFilePath = projectBasename(file.path, projectPath || '')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.workspaceDidCreateFiles({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFileRename = (
|
||||||
|
oldFile: FileEntry,
|
||||||
|
newFile: FileEntry,
|
||||||
|
projectPath: string | null
|
||||||
|
) => {
|
||||||
|
const oldFilePath = projectBasename(oldFile.path, projectPath || '')
|
||||||
|
const newFilePath = projectBasename(newFile.path, projectPath || '')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.workspaceDidRenameFiles({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
oldUri: `file:///${oldFilePath}`,
|
||||||
|
newUri: `file:///${newFilePath}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFileDelete = (file: FileEntry, projectPath: string | null) => {
|
||||||
|
const currentFilePath = projectBasename(file.path, projectPath || '')
|
||||||
|
lspClients.forEach((lspClient) => {
|
||||||
|
lspClient.workspaceDidDeleteFiles({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
uri: `file:///${currentFilePath}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LspStateContext.Provider
|
<LspStateContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -177,6 +286,11 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
kclLSP,
|
kclLSP,
|
||||||
onProjectClose,
|
onProjectClose,
|
||||||
onProjectOpen,
|
onProjectOpen,
|
||||||
|
onFileOpen,
|
||||||
|
onFileClose,
|
||||||
|
onFileCreate,
|
||||||
|
onFileRename,
|
||||||
|
onFileDelete,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -28,7 +28,7 @@ const ProjectSidebarMenu = ({
|
|||||||
<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
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onProjectClose(file || null, false)
|
onProjectClose(file || null, project?.path || null, false)
|
||||||
}}
|
}}
|
||||||
to={paths.HOME}
|
to={paths.HOME}
|
||||||
className="group"
|
className="group"
|
||||||
@ -39,7 +39,7 @@ const ProjectSidebarMenu = ({
|
|||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onProjectClose(file || null, false)
|
onProjectClose(file || null, project?.path || null, false)
|
||||||
}}
|
}}
|
||||||
to={paths.HOME}
|
to={paths.HOME}
|
||||||
className="!no-underline"
|
className="!no-underline"
|
||||||
@ -163,7 +163,7 @@ function ProjectMenuPopover({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onProjectClose(file || null, true)
|
onProjectClose(file || null, project?.path || null, true)
|
||||||
}}
|
}}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faHome,
|
icon: faHome,
|
||||||
|
@ -90,6 +90,9 @@ interface LSPNotifyMap {
|
|||||||
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
||||||
'textDocument/didClose': LSP.DidCloseTextDocumentParams
|
'textDocument/didClose': LSP.DidCloseTextDocumentParams
|
||||||
'workspace/didChangeWorkspaceFolders': LSP.DidChangeWorkspaceFoldersParams
|
'workspace/didChangeWorkspaceFolders': LSP.DidChangeWorkspaceFoldersParams
|
||||||
|
'workspace/didCreateFiles': LSP.CreateFilesParams
|
||||||
|
'workspace/didRenameFiles': LSP.RenameFilesParams
|
||||||
|
'workspace/didDeleteFiles': LSP.DeleteFilesParams
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LanguageServerClientOptions {
|
export interface LanguageServerClientOptions {
|
||||||
@ -187,6 +190,18 @@ export class LanguageServerClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workspaceDidCreateFiles(params: LSP.CreateFilesParams) {
|
||||||
|
this.notify('workspace/didCreateFiles', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceDidRenameFiles(params: LSP.RenameFilesParams) {
|
||||||
|
this.notify('workspace/didRenameFiles', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceDidDeleteFiles(params: LSP.DeleteFilesParams) {
|
||||||
|
this.notify('workspace/didDeleteFiles', params)
|
||||||
|
}
|
||||||
|
|
||||||
async updateSemanticTokens(uri: string) {
|
async updateSemanticTokens(uri: string) {
|
||||||
const serverCapabilities = this.getServerCapabilities()
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
if (!serverCapabilities.semanticTokensProvider) {
|
if (!serverCapabilities.semanticTokensProvider) {
|
||||||
|
@ -39,7 +39,7 @@ export default class Server {
|
|||||||
}
|
}
|
||||||
await copilotLspRun(config, token)
|
await copilotLspRun(config, token)
|
||||||
} else if (type_ === 'kcl') {
|
} else if (type_ === 'kcl') {
|
||||||
await kclLspRun(config)
|
await kclLspRun(config, token || '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
|||||||
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||||
import { Coords2d } from './std/sketch'
|
import { Coords2d } from './std/sketch'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
|
import { DEV } from 'env'
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||||
@ -286,23 +287,19 @@ export function programMemoryInit(): ProgramMemory {
|
|||||||
export async function copilotLspRun(config: ServerConfig, token: string) {
|
export async function copilotLspRun(config: ServerConfig, token: string) {
|
||||||
try {
|
try {
|
||||||
console.log('starting copilot lsp')
|
console.log('starting copilot lsp')
|
||||||
await copilot_lsp_run(config, token)
|
await copilot_lsp_run(config, token, DEV)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('copilot lsp failed', e)
|
console.log('copilot lsp failed', e)
|
||||||
// We make it restart recursively so that if it ever dies after like
|
// We can't restart here because a moved value, we should do this another way.
|
||||||
// 8 hours or something it will come back to life.
|
|
||||||
await copilotLspRun(config, token)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function kclLspRun(config: ServerConfig) {
|
export async function kclLspRun(config: ServerConfig, token: string) {
|
||||||
try {
|
try {
|
||||||
console.log('start kcl lsp')
|
console.log('start kcl lsp')
|
||||||
await kcl_lsp_run(config)
|
await kcl_lsp_run(config, token, DEV)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('kcl lsp failed', e)
|
console.log('kcl lsp failed', e)
|
||||||
// We make it restart recursively so that if it ever dies after like
|
// We can't restart here because a moved value, we should do this another way.
|
||||||
// 8 hours or something it will come back to life.
|
|
||||||
await kclLspRun(config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/wasm-lib/Cargo.lock
generated
20
src/wasm-lib/Cargo.lock
generated
@ -962,6 +962,22 @@ dependencies = [
|
|||||||
"syn 2.0.52",
|
"syn 2.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive-docs"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc85a0d10f808387cd56147b520be7efd94b8b198b1453f987b133cd2a90b7e"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_tokenstream",
|
||||||
|
"syn 2.0.52",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel_derives"
|
name = "diesel_derives"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
@ -1891,7 +1907,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1902,7 +1918,7 @@ dependencies = [
|
|||||||
"criterion",
|
"criterion",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"databake",
|
"databake",
|
||||||
"derive-docs",
|
"derive-docs 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
"futures",
|
"futures",
|
||||||
"gltf-json",
|
"gltf-json",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -17,8 +17,8 @@ async-trait = "0.1.77"
|
|||||||
clap = { version = "4.5.2", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
clap = { version = "4.5.2", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
databake = { version = "0.1.7", features = ["derive"] }
|
databake = { version = "0.1.7", features = ["derive"] }
|
||||||
#derive-docs = { version = "0.1.10" }
|
derive-docs = { version = "0.1.10" }
|
||||||
derive-docs = { path = "../derive-docs" }
|
#derive-docs = { path = "../derive-docs" }
|
||||||
futures = { version = "0.3.30" }
|
futures = { version = "0.3.30" }
|
||||||
gltf-json = "1.4.0"
|
gltf-json = "1.4.0"
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
|
@ -158,7 +158,7 @@ impl EngineConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl EngineManager for EngineConnection {
|
impl EngineManager for EngineConnection {
|
||||||
async fn send_modeling_cmd(
|
async fn send_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
|
@ -15,7 +15,7 @@ impl EngineConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl crate::engine::EngineManager for EngineConnection {
|
impl crate::engine::EngineManager for EngineConnection {
|
||||||
async fn send_modeling_cmd(
|
async fn send_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
|
@ -27,6 +27,10 @@ pub struct EngineConnection {
|
|||||||
manager: Arc<EngineCommandManager>,
|
manager: Arc<EngineCommandManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||||
|
unsafe impl Send for EngineConnection {}
|
||||||
|
unsafe impl Sync for EngineConnection {}
|
||||||
|
|
||||||
impl EngineConnection {
|
impl EngineConnection {
|
||||||
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
||||||
Ok(EngineConnection {
|
Ok(EngineConnection {
|
||||||
@ -35,7 +39,7 @@ impl EngineConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl crate::engine::EngineManager for EngineConnection {
|
impl crate::engine::EngineManager for EngineConnection {
|
||||||
async fn send_modeling_cmd(
|
async fn send_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
@ -67,7 +71,7 @@ impl crate::engine::EngineManager for EngineConnection {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
|
@ -31,7 +31,7 @@ use anyhow::Result;
|
|||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub use conn_mock::EngineConnection;
|
pub use conn_mock::EngineConnection;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
pub trait EngineManager: Clone {
|
pub trait EngineManager: Clone {
|
||||||
/// Send a modeling command and wait for the response message.
|
/// Send a modeling command and wait for the response message.
|
||||||
async fn send_modeling_cmd(
|
async fn send_modeling_cmd(
|
||||||
|
@ -22,9 +22,9 @@ impl Default for FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl FileSystem for FileManager {
|
impl FileSystem for FileManager {
|
||||||
async fn read<P: AsRef<std::path::Path>>(
|
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -37,7 +37,7 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exists<P: AsRef<std::path::Path>>(
|
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -54,7 +54,7 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_files<P: AsRef<std::path::Path>>(
|
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
|
@ -13,24 +13,24 @@ use anyhow::Result;
|
|||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub use wasm::FileManager;
|
pub use wasm::FileManager;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
pub trait FileSystem: Clone {
|
pub trait FileSystem: Clone {
|
||||||
/// Read a file from the local file system.
|
/// Read a file from the local file system.
|
||||||
async fn read<P: AsRef<std::path::Path>>(
|
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
) -> Result<Vec<u8>, crate::errors::KclError>;
|
) -> Result<Vec<u8>, crate::errors::KclError>;
|
||||||
|
|
||||||
/// Check if a file exists on the local file system.
|
/// Check if a file exists on the local file system.
|
||||||
async fn exists<P: AsRef<std::path::Path>>(
|
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
) -> Result<bool, crate::errors::KclError>;
|
) -> Result<bool, crate::errors::KclError>;
|
||||||
|
|
||||||
/// Get all the files in a directory recursively.
|
/// Get all the files in a directory recursively.
|
||||||
async fn get_all_files<P: AsRef<std::path::Path>>(
|
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
|
@ -6,6 +6,7 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
fs::FileSystem,
|
fs::FileSystem,
|
||||||
|
wasm::JsFuture,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[wasm_bindgen(module = "/../../lang/std/fileSystemManager.ts")]
|
#[wasm_bindgen(module = "/../../lang/std/fileSystemManager.ts")]
|
||||||
@ -37,9 +38,9 @@ impl FileManager {
|
|||||||
unsafe impl Send for FileManager {}
|
unsafe impl Send for FileManager {}
|
||||||
unsafe impl Sync for FileManager {}
|
unsafe impl Sync for FileManager {}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl FileSystem for FileManager {
|
impl FileSystem for FileManager {
|
||||||
async fn read<P: AsRef<std::path::Path>>(
|
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -64,7 +65,7 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
@ -77,7 +78,7 @@ impl FileSystem for FileManager {
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exists<P: AsRef<std::path::Path>>(
|
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -102,7 +103,7 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
@ -119,7 +120,7 @@ impl FileSystem for FileManager {
|
|||||||
Ok(it_exists)
|
Ok(it_exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_files<P: AsRef<std::path::Path>>(
|
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -144,7 +145,7 @@ impl FileSystem for FileManager {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to wait for promise from javascript: {:?}", e),
|
message: format!("Failed to wait for promise from javascript: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
|
@ -14,3 +14,5 @@ pub mod lsp;
|
|||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod std;
|
pub mod std;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod wasm;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! A shared backend trait for lsp servers memory and behavior.
|
//! A shared backend trait for lsp servers memory and behavior.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tower_lsp::lsp_types::{
|
use tower_lsp::lsp_types::{
|
||||||
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
||||||
@ -8,6 +9,8 @@ use tower_lsp::lsp_types::{
|
|||||||
TextDocumentItem, WorkspaceFolder,
|
TextDocumentItem, WorkspaceFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::fs::FileSystem;
|
||||||
|
|
||||||
/// A trait for the backend of the language server.
|
/// A trait for the backend of the language server.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
@ -22,10 +25,13 @@ pub trait Backend {
|
|||||||
fn remove_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, Vec<u8>>;
|
||||||
|
|
||||||
/// 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: Vec<u8>);
|
||||||
|
|
||||||
|
// Remove from code map.
|
||||||
|
fn remove_from_code_map(&self, uri: String) -> Option<(String, Vec<u8>)>;
|
||||||
|
|
||||||
/// Clear the current code state.
|
/// Clear the current code state.
|
||||||
fn clear_code_state(&self);
|
fn clear_code_state(&self);
|
||||||
@ -34,8 +40,25 @@ pub trait Backend {
|
|||||||
async fn on_change(&self, params: TextDocumentItem);
|
async fn on_change(&self, params: TextDocumentItem);
|
||||||
|
|
||||||
async fn update_memory(&self, params: TextDocumentItem) {
|
async fn update_memory(&self, params: TextDocumentItem) {
|
||||||
// Lets update the tokens.
|
self.insert_current_code_map(params.uri.to_string(), params.text.as_bytes().to_vec());
|
||||||
self.insert_current_code_map(params.uri.to_string(), params.text.clone());
|
}
|
||||||
|
|
||||||
|
async fn update_from_disk<P: AsRef<std::path::Path> + std::marker::Send>(&self, path: P) -> Result<()> {
|
||||||
|
// Read over all the files in the directory and add them to our current code map.
|
||||||
|
let files = self.fs().get_all_files(path.as_ref(), Default::default()).await?;
|
||||||
|
for file in files {
|
||||||
|
// Read the file.
|
||||||
|
let contents = self.fs().read(&file, Default::default()).await?;
|
||||||
|
let file_path = format!(
|
||||||
|
"file://{}",
|
||||||
|
file.as_path()
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("could not get name of file: {:?}", file))?
|
||||||
|
);
|
||||||
|
self.insert_current_code_map(file_path, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_initialized(&self, params: InitializedParams) {
|
async fn do_initialized(&self, params: InitializedParams) {
|
||||||
@ -52,11 +75,23 @@ pub trait Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
|
async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
|
||||||
self.add_workspace_folders(params.event.added);
|
self.add_workspace_folders(params.event.added.clone());
|
||||||
self.remove_workspace_folders(params.event.removed);
|
self.remove_workspace_folders(params.event.removed);
|
||||||
// Remove the code from the current code map.
|
// 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.
|
// We do this since it means the user is changing projects so let's refresh the state.
|
||||||
self.clear_code_state();
|
self.clear_code_state();
|
||||||
|
for added in params.event.added {
|
||||||
|
// Try to read all the files in the project.
|
||||||
|
let project_dir = added.uri.to_string().replace("file://", "");
|
||||||
|
if let Err(err) = self.update_from_disk(&project_dir).await {
|
||||||
|
self.client()
|
||||||
|
.log_message(
|
||||||
|
MessageType::WARNING,
|
||||||
|
format!("updating from disk `{}` failed: {:?}", project_dir, err),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) {
|
async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) {
|
||||||
@ -75,18 +110,36 @@ pub trait Backend {
|
|||||||
self.client()
|
self.client()
|
||||||
.log_message(MessageType::INFO, format!("files created: {:?}", params))
|
.log_message(MessageType::INFO, format!("files created: {:?}", params))
|
||||||
.await;
|
.await;
|
||||||
|
// Create each file in the code map.
|
||||||
|
for file in params.files {
|
||||||
|
self.insert_current_code_map(file.uri.to_string(), Default::default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_rename_files(&self, params: RenameFilesParams) {
|
async fn do_did_rename_files(&self, params: RenameFilesParams) {
|
||||||
self.client()
|
self.client()
|
||||||
.log_message(MessageType::INFO, format!("files renamed: {:?}", params))
|
.log_message(MessageType::INFO, format!("files renamed: {:?}", params))
|
||||||
.await;
|
.await;
|
||||||
|
// Rename each file in the code map.
|
||||||
|
for file in params.files {
|
||||||
|
if let Some((_, value)) = self.remove_from_code_map(file.old_uri) {
|
||||||
|
// Rename the file if it exists.
|
||||||
|
self.insert_current_code_map(file.new_uri.to_string(), value);
|
||||||
|
} else {
|
||||||
|
// Otherwise create it.
|
||||||
|
self.insert_current_code_map(file.new_uri.to_string(), Default::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_delete_files(&self, params: DeleteFilesParams) {
|
async fn do_did_delete_files(&self, params: DeleteFilesParams) {
|
||||||
self.client()
|
self.client()
|
||||||
.log_message(MessageType::INFO, format!("files deleted: {:?}", params))
|
.log_message(MessageType::INFO, format!("files deleted: {:?}", params))
|
||||||
.await;
|
.await;
|
||||||
|
// Delete each file in the map.
|
||||||
|
for file in params.files {
|
||||||
|
self.remove_from_code_map(file.uri.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_did_open(&self, params: DidOpenTextDocumentParams) {
|
async fn do_did_open(&self, params: DidOpenTextDocumentParams) {
|
||||||
|
@ -48,9 +48,9 @@ pub struct Backend {
|
|||||||
/// The workspace folders.
|
/// The workspace folders.
|
||||||
pub workspace_folders: DashMap<String, WorkspaceFolder>,
|
pub workspace_folders: DashMap<String, WorkspaceFolder>,
|
||||||
/// Current code.
|
/// Current code.
|
||||||
pub current_code_map: DashMap<String, String>,
|
pub current_code_map: DashMap<String, Vec<u8>>,
|
||||||
/// The token is used to authenticate requests to the API server.
|
/// The Zoo API client.
|
||||||
pub token: String,
|
pub zoo_client: kittycad::Client,
|
||||||
/// The editor info is used to store information about the editor.
|
/// The editor info is used to store information about the editor.
|
||||||
pub editor_info: Arc<RwLock<CopilotEditorInfo>>,
|
pub editor_info: Arc<RwLock<CopilotEditorInfo>>,
|
||||||
/// The cache is used to store the results of previous requests.
|
/// The cache is used to store the results of previous requests.
|
||||||
@ -84,14 +84,18 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, Vec<u8>> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_current_code_map(&self, uri: String, text: String) {
|
fn insert_current_code_map(&self, uri: String, text: Vec<u8>) {
|
||||||
self.current_code_map.insert(uri, text);
|
self.current_code_map.insert(uri, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_from_code_map(&self, uri: String) -> Option<(String, Vec<u8>)> {
|
||||||
|
self.current_code_map.remove(&uri)
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_code_state(&self) {
|
fn clear_code_state(&self) {
|
||||||
self.current_code_map.clear();
|
self.current_code_map.clear();
|
||||||
}
|
}
|
||||||
@ -125,8 +129,8 @@ impl Backend {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let kc_client = kittycad::Client::new(&self.token);
|
let resp = self
|
||||||
let resp = kc_client
|
.zoo_client
|
||||||
.ai()
|
.ai()
|
||||||
.create_kcl_code_completions(&body)
|
.create_kcl_code_completions(&body)
|
||||||
.await
|
.await
|
||||||
|
@ -62,13 +62,17 @@ pub struct Backend {
|
|||||||
/// AST maps.
|
/// AST maps.
|
||||||
pub ast_map: DashMap<String, crate::ast::types::Program>,
|
pub ast_map: DashMap<String, crate::ast::types::Program>,
|
||||||
/// Current code.
|
/// Current code.
|
||||||
pub current_code_map: DashMap<String, String>,
|
pub current_code_map: DashMap<String, Vec<u8>>,
|
||||||
/// Diagnostics.
|
/// Diagnostics.
|
||||||
pub diagnostics_map: DashMap<String, DocumentDiagnosticReport>,
|
pub diagnostics_map: DashMap<String, DocumentDiagnosticReport>,
|
||||||
/// Symbols map.
|
/// Symbols map.
|
||||||
pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
|
pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
|
||||||
/// Semantic tokens map.
|
/// Semantic tokens map.
|
||||||
pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
|
pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
|
||||||
|
/// The Zoo API client.
|
||||||
|
pub zoo_client: kittycad::Client,
|
||||||
|
/// If we can send telemetry for this user.
|
||||||
|
pub can_send_telemetry: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the shared backend trait for the language server.
|
// Implement the shared backend trait for the language server.
|
||||||
@ -98,14 +102,18 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_code_map(&self) -> DashMap<String, String> {
|
fn current_code_map(&self) -> DashMap<String, Vec<u8>> {
|
||||||
self.current_code_map.clone()
|
self.current_code_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_current_code_map(&self, uri: String, text: String) {
|
fn insert_current_code_map(&self, uri: String, text: Vec<u8>) {
|
||||||
self.current_code_map.insert(uri, text);
|
self.current_code_map.insert(uri, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_from_code_map(&self, uri: String) -> Option<(String, Vec<u8>)> {
|
||||||
|
self.current_code_map.remove(&uri)
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_code_state(&self) {
|
fn clear_code_state(&self) {
|
||||||
self.current_code_map.clear();
|
self.current_code_map.clear();
|
||||||
self.token_map.clear();
|
self.token_map.clear();
|
||||||
@ -403,8 +411,11 @@ impl LanguageServer for Backend {
|
|||||||
let Some(current_code) = self.current_code_map.get(&filename) else {
|
let Some(current_code) = self.current_code_map.get(&filename) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
let Ok(current_code) = std::str::from_utf8(¤t_code) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
let pos = position_to_char_index(params.text_document_position_params.position, ¤t_code);
|
let pos = position_to_char_index(params.text_document_position_params.position, current_code);
|
||||||
|
|
||||||
// Let's iterate over the AST and find the node that contains the cursor.
|
// Let's iterate over the AST and find the node that contains the cursor.
|
||||||
let Some(ast) = self.ast_map.get(&filename) else {
|
let Some(ast) = self.ast_map.get(&filename) else {
|
||||||
@ -415,7 +426,7 @@ impl LanguageServer for Backend {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(hover) = value.get_hover_value_for_position(pos, ¤t_code) else {
|
let Some(hover) = value.get_hover_value_for_position(pos, current_code) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -517,8 +528,11 @@ impl LanguageServer for Backend {
|
|||||||
let Some(current_code) = self.current_code_map.get(&filename) else {
|
let Some(current_code) = self.current_code_map.get(&filename) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
let Ok(current_code) = std::str::from_utf8(¤t_code) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
let pos = position_to_char_index(params.text_document_position_params.position, ¤t_code);
|
let pos = position_to_char_index(params.text_document_position_params.position, current_code);
|
||||||
|
|
||||||
// Let's iterate over the AST and find the node that contains the cursor.
|
// Let's iterate over the AST and find the node that contains the cursor.
|
||||||
let Some(ast) = self.ast_map.get(&filename) else {
|
let Some(ast) = self.ast_map.get(&filename) else {
|
||||||
@ -529,7 +543,7 @@ impl LanguageServer for Backend {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(hover) = value.get_hover_value_for_position(pos, ¤t_code) else {
|
let Some(hover) = value.get_hover_value_for_position(pos, current_code) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -595,11 +609,14 @@ impl LanguageServer for Backend {
|
|||||||
let Some(current_code) = self.current_code_map.get(&filename) else {
|
let Some(current_code) = self.current_code_map.get(&filename) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
let Ok(current_code) = std::str::from_utf8(¤t_code) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
// Parse the ast.
|
// Parse the ast.
|
||||||
// I don't know if we need to do this again since it should be updated in the context.
|
// I don't know if we need to do this again since it should be updated in the context.
|
||||||
// But I figure better safe than sorry since this will write back out to the file.
|
// But I figure better safe than sorry since this will write back out to the file.
|
||||||
let tokens = crate::token::lexer(¤t_code);
|
let tokens = crate::token::lexer(current_code);
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
let Ok(ast) = parser.ast() else {
|
let Ok(ast) = parser.ast() else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -614,7 +631,7 @@ impl LanguageServer for Backend {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let source_range = SourceRange([0, current_code.len() - 1]);
|
let source_range = SourceRange([0, current_code.len() - 1]);
|
||||||
let range = source_range.to_lsp_range(¤t_code);
|
let range = source_range.to_lsp_range(current_code);
|
||||||
Ok(Some(vec![TextEdit {
|
Ok(Some(vec![TextEdit {
|
||||||
new_text: recast,
|
new_text: recast,
|
||||||
range,
|
range,
|
||||||
@ -627,24 +644,27 @@ impl LanguageServer for Backend {
|
|||||||
let Some(current_code) = self.current_code_map.get(&filename) else {
|
let Some(current_code) = self.current_code_map.get(&filename) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
let Ok(current_code) = std::str::from_utf8(¤t_code) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
// Parse the ast.
|
// Parse the ast.
|
||||||
// I don't know if we need to do this again since it should be updated in the context.
|
// I don't know if we need to do this again since it should be updated in the context.
|
||||||
// But I figure better safe than sorry since this will write back out to the file.
|
// But I figure better safe than sorry since this will write back out to the file.
|
||||||
let tokens = crate::token::lexer(¤t_code);
|
let tokens = crate::token::lexer(current_code);
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
let Ok(mut ast) = parser.ast() else {
|
let Ok(mut ast) = parser.ast() else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Let's convert the position to a character index.
|
// Let's convert the position to a character index.
|
||||||
let pos = position_to_char_index(params.text_document_position.position, ¤t_code);
|
let pos = position_to_char_index(params.text_document_position.position, current_code);
|
||||||
// Now let's perform the rename on the ast.
|
// Now let's perform the rename on the ast.
|
||||||
ast.rename_symbol(¶ms.new_name, pos);
|
ast.rename_symbol(¶ms.new_name, pos);
|
||||||
// Now recast it.
|
// Now recast it.
|
||||||
let recast = ast.recast(&Default::default(), 0);
|
let recast = ast.recast(&Default::default(), 0);
|
||||||
let source_range = SourceRange([0, current_code.len() - 1]);
|
let source_range = SourceRange([0, current_code.len() - 1]);
|
||||||
let range = source_range.to_lsp_range(¤t_code);
|
let range = source_range.to_lsp_range(current_code);
|
||||||
Ok(Some(WorkspaceEdit {
|
Ok(Some(WorkspaceEdit {
|
||||||
changes: Some(HashMap::from([(
|
changes: Some(HashMap::from([(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
|
27
src/wasm-lib/kcl/src/wasm/mod.rs
Normal file
27
src/wasm-lib/kcl/src/wasm/mod.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
///! Web assembly utils.
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A JsFuture that implements Send and Sync.
|
||||||
|
pub struct JsFuture(pub wasm_bindgen_futures::JsFuture);
|
||||||
|
|
||||||
|
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||||
|
unsafe impl Send for JsFuture {}
|
||||||
|
unsafe impl Sync for JsFuture {}
|
||||||
|
|
||||||
|
impl std::future::Future for JsFuture {
|
||||||
|
type Output = Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let mut pinned: Pin<&mut wasm_bindgen_futures::JsFuture> = Pin::new(&mut self.get_mut().0);
|
||||||
|
pinned.as_mut().poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<js_sys::Promise> for JsFuture {
|
||||||
|
fn from(promise: js_sys::Promise) -> JsFuture {
|
||||||
|
JsFuture(wasm_bindgen_futures::JsFuture::from(promise))
|
||||||
|
}
|
||||||
|
}
|
@ -167,7 +167,7 @@ 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, token: String, is_dev: bool) -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
@ -183,6 +183,28 @@ pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
|||||||
// we have a test for it.
|
// we have a test for it.
|
||||||
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
let token_types = kcl_lib::token::TokenType::all_semantic_token_types().unwrap();
|
||||||
|
|
||||||
|
let mut zoo_client = kittycad::Client::new(token);
|
||||||
|
if is_dev {
|
||||||
|
zoo_client.set_base_url("https://api.dev.zoo.dev");
|
||||||
|
}
|
||||||
|
// Check if we can send telememtry for this user.
|
||||||
|
let privacy_settings = match zoo_client.users().get_privacy_settings().await {
|
||||||
|
Ok(privacy_settings) => privacy_settings,
|
||||||
|
Err(err) => {
|
||||||
|
// In the case of dev we don't always have a sub set, but prod we should.
|
||||||
|
if err
|
||||||
|
.to_string()
|
||||||
|
.contains("The modeling app subscription type is missing.")
|
||||||
|
{
|
||||||
|
kittycad::types::PrivacySettings {
|
||||||
|
can_train_on_data: true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(err.to_string().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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),
|
||||||
@ -196,6 +218,8 @@ pub async fn kcl_lsp_run(config: ServerConfig) -> Result<(), JsValue> {
|
|||||||
diagnostics_map: Default::default(),
|
diagnostics_map: Default::default(),
|
||||||
symbols_map: Default::default(),
|
symbols_map: Default::default(),
|
||||||
semantic_tokens_map: Default::default(),
|
semantic_tokens_map: Default::default(),
|
||||||
|
zoo_client,
|
||||||
|
can_send_telemetry: privacy_settings.can_train_on_data,
|
||||||
});
|
});
|
||||||
|
|
||||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||||
@ -226,7 +250,7 @@ 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, is_dev: bool) -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let ServerConfig {
|
let ServerConfig {
|
||||||
@ -235,6 +259,11 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(),
|
|||||||
fs,
|
fs,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
|
let mut zoo_client = kittycad::Client::new(token);
|
||||||
|
if is_dev {
|
||||||
|
zoo_client.set_base_url("https://api.dev.zoo.dev");
|
||||||
|
}
|
||||||
|
|
||||||
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),
|
||||||
@ -242,7 +271,7 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String) -> Result<(),
|
|||||||
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(),
|
||||||
token,
|
zoo_client,
|
||||||
})
|
})
|
||||||
.custom_method("setEditorInfo", kcl_lib::lsp::copilot::Backend::set_editor_info)
|
.custom_method("setEditorInfo", kcl_lib::lsp::copilot::Backend::set_editor_info)
|
||||||
.custom_method(
|
.custom_method(
|
||||||
|
Reference in New Issue
Block a user