rird 1.0.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/opencode +336 -0
- package/bin/pty-wrapper.js +285 -0
- package/bunfig.toml +4 -0
- package/facebook_ads_library.png +0 -0
- package/nul`nif +0 -0
- package/package.json +111 -0
- package/parsers-config.ts +239 -0
- package/rird-1.0.199.tgz +0 -0
- package/script/build-windows.ts +54 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +544 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +72 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1063 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +367 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +10 -0
- package/src/agent/prompt/title.txt +36 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +114 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +256 -0
- package/src/cli/cmd/auth.ts +391 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +48 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1400 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +654 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +368 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +696 -0
- package/src/cli/cmd/tui/attach.ts +30 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
- package/src/cli/cmd/tui/component/logo.tsx +35 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/tips.ts +27 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +354 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +74 -0
- package/src/cli/cmd/tui/context/sync.tsx +372 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rird.json +245 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1109 -0
- package/src/cli/cmd/tui/event.ts +40 -0
- package/src/cli/cmd/tui/routes/home.tsx +138 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +142 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +100 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +56 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +80 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +995 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +328 -0
- package/src/file/ripgrep.ts +393 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +103 -0
- package/src/flag/flag.ts +46 -0
- package/src/format/formatter.ts +315 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +240 -0
- package/src/installation/index.ts +239 -0
- package/src/lsp/client.ts +229 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +116 -0
- package/src/lsp/server.ts +1895 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +690 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +199 -0
- package/src/plugin/index.ts +91 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +78 -0
- package/src/project/project.ts +221 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +106 -0
- package/src/provider/provider.ts +1071 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +455 -0
- package/src/pty/index.ts +231 -0
- package/src/security/guardrails.test.ts +341 -0
- package/src/security/guardrails.ts +558 -0
- package/src/security/index.ts +19 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +79 -0
- package/src/server/server.ts +2642 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +223 -0
- package/src/session/index.ts +461 -0
- package/src/session/llm.ts +201 -0
- package/src/session/message-v2.ts +690 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +409 -0
- package/src/session/prompt/act-switch.txt +5 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +85 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +103 -0
- package/src/session/prompt/codex.txt +304 -0
- package/src/session/prompt/copilot-gpt-5.txt +138 -0
- package/src/session/prompt/gemini.txt +85 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
- package/src/session/prompt/plan.txt +24 -0
- package/src/session/prompt/polaris.txt +84 -0
- package/src/session/prompt/qwen.txt +106 -0
- package/src/session/prompt.ts +1509 -0
- package/src/session/retry.ts +86 -0
- package/src/session/revert.ts +108 -0
- package/src/session/sensitive-filter.test.ts +327 -0
- package/src/session/sensitive-filter.ts +466 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +194 -0
- package/src/session/system.ts +120 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +83 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tests/agent.test.ts +308 -0
- package/src/tests/build-guards.test.ts +267 -0
- package/src/tests/config.test.ts +664 -0
- package/src/tests/tool-registry.test.ts +589 -0
- package/src/tool/bash.ts +317 -0
- package/src/tool/bash.txt +158 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +168 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +675 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +121 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/lsp.ts +87 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +219 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +100 -0
- package/src/tool/task.ts +136 -0
- package/src/tool/task.txt +51 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +71 -0
- package/src/tool/webfetch.ts +198 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +180 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +110 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +83 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +102 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/license.ts +325 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
- package/sst-env.d.ts +9 -0
- package/test/agent/agent.test.ts +146 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +535 -0
- package/test/config/markdown.test.ts +89 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/fixture/fixture.ts +36 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/preload.ts +57 -0
- package/test/project/project.test.ts +72 -0
- package/test/provider/provider.test.ts +1809 -0
- package/test/provider/transform.test.ts +411 -0
- package/test/session/retry.test.ts +111 -0
- package/test/session/session.test.ts +71 -0
- package/test/skill/skill.test.ts +131 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/bash.test.ts +434 -0
- package/test/tool/grep.test.ts +108 -0
- package/test/tool/patch.test.ts +259 -0
- package/test/tool/read.test.ts +42 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +55 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,1895 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import os from "os"
|
|
4
|
+
import { Global } from "../global"
|
|
5
|
+
import { Log } from "../util/log"
|
|
6
|
+
import { BunProc } from "../bun"
|
|
7
|
+
import { $, readableStreamToText } from "bun"
|
|
8
|
+
import fs from "fs/promises"
|
|
9
|
+
import { Filesystem } from "../util/filesystem"
|
|
10
|
+
import { Instance } from "../project/instance"
|
|
11
|
+
import { Flag } from "../flag/flag"
|
|
12
|
+
import { Archive } from "../util/archive"
|
|
13
|
+
|
|
14
|
+
export namespace LSPServer {
|
|
15
|
+
const log = Log.create({ service: "lsp.server" })
|
|
16
|
+
|
|
17
|
+
export interface Handle {
|
|
18
|
+
process: ChildProcessWithoutNullStreams
|
|
19
|
+
initialization?: Record<string, any>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type RootFunction = (file: string) => Promise<string | undefined>
|
|
23
|
+
|
|
24
|
+
const NearestRoot = (includePatterns: string[], excludePatterns?: string[]): RootFunction => {
|
|
25
|
+
return async (file) => {
|
|
26
|
+
if (excludePatterns) {
|
|
27
|
+
const excludedFiles = Filesystem.up({
|
|
28
|
+
targets: excludePatterns,
|
|
29
|
+
start: path.dirname(file),
|
|
30
|
+
stop: Instance.directory,
|
|
31
|
+
})
|
|
32
|
+
const excluded = await excludedFiles.next()
|
|
33
|
+
await excludedFiles.return()
|
|
34
|
+
if (excluded.value) return undefined
|
|
35
|
+
}
|
|
36
|
+
const files = Filesystem.up({
|
|
37
|
+
targets: includePatterns,
|
|
38
|
+
start: path.dirname(file),
|
|
39
|
+
stop: Instance.directory,
|
|
40
|
+
})
|
|
41
|
+
const first = await files.next()
|
|
42
|
+
await files.return()
|
|
43
|
+
if (!first.value) return Instance.directory
|
|
44
|
+
return path.dirname(first.value)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Info {
|
|
49
|
+
id: string
|
|
50
|
+
extensions: string[]
|
|
51
|
+
global?: boolean
|
|
52
|
+
root: RootFunction
|
|
53
|
+
spawn(root: string): Promise<Handle | undefined>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Deno: Info = {
|
|
57
|
+
id: "deno",
|
|
58
|
+
root: async (file) => {
|
|
59
|
+
const files = Filesystem.up({
|
|
60
|
+
targets: ["deno.json", "deno.jsonc"],
|
|
61
|
+
start: path.dirname(file),
|
|
62
|
+
stop: Instance.directory,
|
|
63
|
+
})
|
|
64
|
+
const first = await files.next()
|
|
65
|
+
await files.return()
|
|
66
|
+
if (!first.value) return undefined
|
|
67
|
+
return path.dirname(first.value)
|
|
68
|
+
},
|
|
69
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
|
|
70
|
+
async spawn(root) {
|
|
71
|
+
const deno = Bun.which("deno")
|
|
72
|
+
if (!deno) {
|
|
73
|
+
log.info("deno not found, please install deno first")
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
process: spawn(deno, ["lsp"], {
|
|
78
|
+
cwd: root,
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Typescript: Info = {
|
|
85
|
+
id: "typescript",
|
|
86
|
+
root: NearestRoot(
|
|
87
|
+
["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"],
|
|
88
|
+
["deno.json", "deno.jsonc"],
|
|
89
|
+
),
|
|
90
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
|
91
|
+
async spawn(root) {
|
|
92
|
+
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {})
|
|
93
|
+
log.info("typescript server", { tsserver })
|
|
94
|
+
if (!tsserver) return
|
|
95
|
+
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
|
|
96
|
+
cwd: root,
|
|
97
|
+
env: {
|
|
98
|
+
...process.env,
|
|
99
|
+
BUN_BE_BUN: "1",
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
return {
|
|
103
|
+
process: proc,
|
|
104
|
+
initialization: {
|
|
105
|
+
tsserver: {
|
|
106
|
+
path: tsserver,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const Vue: Info = {
|
|
114
|
+
id: "vue",
|
|
115
|
+
extensions: [".vue"],
|
|
116
|
+
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
|
117
|
+
async spawn(root) {
|
|
118
|
+
let binary = Bun.which("vue-language-server")
|
|
119
|
+
const args: string[] = []
|
|
120
|
+
if (!binary) {
|
|
121
|
+
const js = path.join(
|
|
122
|
+
Global.Path.bin,
|
|
123
|
+
"node_modules",
|
|
124
|
+
"@vue",
|
|
125
|
+
"language-server",
|
|
126
|
+
"bin",
|
|
127
|
+
"vue-language-server.js",
|
|
128
|
+
)
|
|
129
|
+
if (!(await Bun.file(js).exists())) {
|
|
130
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
131
|
+
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
|
132
|
+
cwd: Global.Path.bin,
|
|
133
|
+
env: {
|
|
134
|
+
...process.env,
|
|
135
|
+
BUN_BE_BUN: "1",
|
|
136
|
+
},
|
|
137
|
+
stdout: "pipe",
|
|
138
|
+
stderr: "pipe",
|
|
139
|
+
stdin: "pipe",
|
|
140
|
+
}).exited
|
|
141
|
+
}
|
|
142
|
+
binary = BunProc.which()
|
|
143
|
+
args.push("run", js)
|
|
144
|
+
}
|
|
145
|
+
args.push("--stdio")
|
|
146
|
+
const proc = spawn(binary, args, {
|
|
147
|
+
cwd: root,
|
|
148
|
+
env: {
|
|
149
|
+
...process.env,
|
|
150
|
+
BUN_BE_BUN: "1",
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
return {
|
|
154
|
+
process: proc,
|
|
155
|
+
initialization: {
|
|
156
|
+
// Leave empty; the server will auto-detect workspace TypeScript.
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const ESLint: Info = {
|
|
163
|
+
id: "eslint",
|
|
164
|
+
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
|
165
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"],
|
|
166
|
+
async spawn(root) {
|
|
167
|
+
const eslint = await Bun.resolve("eslint", Instance.directory).catch(() => {})
|
|
168
|
+
if (!eslint) return
|
|
169
|
+
log.info("spawning eslint server")
|
|
170
|
+
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
|
|
171
|
+
if (!(await Bun.file(serverPath).exists())) {
|
|
172
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
173
|
+
log.info("downloading and building VS Code ESLint server")
|
|
174
|
+
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
|
|
175
|
+
if (!response.ok) return
|
|
176
|
+
|
|
177
|
+
const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
|
|
178
|
+
await Bun.file(zipPath).write(response)
|
|
179
|
+
|
|
180
|
+
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
|
181
|
+
.then(() => true)
|
|
182
|
+
.catch((error) => {
|
|
183
|
+
log.error("Failed to extract vscode-eslint archive", { error })
|
|
184
|
+
return false
|
|
185
|
+
})
|
|
186
|
+
if (!ok) return
|
|
187
|
+
await fs.rm(zipPath, { force: true })
|
|
188
|
+
|
|
189
|
+
const extractedPath = path.join(Global.Path.bin, "vscode-eslint-main")
|
|
190
|
+
const finalPath = path.join(Global.Path.bin, "vscode-eslint")
|
|
191
|
+
|
|
192
|
+
const stats = await fs.stat(finalPath).catch(() => undefined)
|
|
193
|
+
if (stats) {
|
|
194
|
+
log.info("removing old eslint installation", { path: finalPath })
|
|
195
|
+
await fs.rm(finalPath, { force: true, recursive: true })
|
|
196
|
+
}
|
|
197
|
+
await fs.rename(extractedPath, finalPath)
|
|
198
|
+
|
|
199
|
+
await $`npm install`.cwd(finalPath).quiet()
|
|
200
|
+
await $`npm run compile`.cwd(finalPath).quiet()
|
|
201
|
+
|
|
202
|
+
log.info("installed VS Code ESLint server", { serverPath })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const proc = spawn(BunProc.which(), ["--max-old-space-size=8192", serverPath, "--stdio"], {
|
|
206
|
+
cwd: root,
|
|
207
|
+
env: {
|
|
208
|
+
...process.env,
|
|
209
|
+
BUN_BE_BUN: "1",
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
process: proc,
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export const Oxlint: Info = {
|
|
220
|
+
id: "oxlint",
|
|
221
|
+
root: NearestRoot([
|
|
222
|
+
".oxlintrc.json",
|
|
223
|
+
"package-lock.json",
|
|
224
|
+
"bun.lockb",
|
|
225
|
+
"bun.lock",
|
|
226
|
+
"pnpm-lock.yaml",
|
|
227
|
+
"yarn.lock",
|
|
228
|
+
"package.json",
|
|
229
|
+
]),
|
|
230
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".astro", ".svelte"],
|
|
231
|
+
async spawn(root) {
|
|
232
|
+
const ext = process.platform === "win32" ? ".cmd" : ""
|
|
233
|
+
|
|
234
|
+
const serverTarget = path.join("node_modules", ".bin", "oxc_language_server" + ext)
|
|
235
|
+
const lintTarget = path.join("node_modules", ".bin", "oxlint" + ext)
|
|
236
|
+
|
|
237
|
+
const resolveBin = async (target: string) => {
|
|
238
|
+
const localBin = path.join(root, target)
|
|
239
|
+
if (await Bun.file(localBin).exists()) return localBin
|
|
240
|
+
|
|
241
|
+
const candidates = Filesystem.up({
|
|
242
|
+
targets: [target],
|
|
243
|
+
start: root,
|
|
244
|
+
stop: Instance.worktree,
|
|
245
|
+
})
|
|
246
|
+
const first = await candidates.next()
|
|
247
|
+
await candidates.return()
|
|
248
|
+
if (first.value) return first.value
|
|
249
|
+
|
|
250
|
+
return undefined
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let lintBin = await resolveBin(lintTarget)
|
|
254
|
+
if (!lintBin) {
|
|
255
|
+
const found = Bun.which("oxlint")
|
|
256
|
+
if (found) lintBin = found
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (lintBin) {
|
|
260
|
+
const proc = Bun.spawn([lintBin, "--help"], { stdout: "pipe" })
|
|
261
|
+
await proc.exited
|
|
262
|
+
const help = await readableStreamToText(proc.stdout)
|
|
263
|
+
if (help.includes("--lsp")) {
|
|
264
|
+
return {
|
|
265
|
+
process: spawn(lintBin, ["--lsp"], {
|
|
266
|
+
cwd: root,
|
|
267
|
+
}),
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let serverBin = await resolveBin(serverTarget)
|
|
273
|
+
if (!serverBin) {
|
|
274
|
+
const found = Bun.which("oxc_language_server")
|
|
275
|
+
if (found) serverBin = found
|
|
276
|
+
}
|
|
277
|
+
if (serverBin) {
|
|
278
|
+
return {
|
|
279
|
+
process: spawn(serverBin, [], {
|
|
280
|
+
cwd: root,
|
|
281
|
+
}),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
log.info("oxlint not found, please install oxlint")
|
|
286
|
+
return
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const Biome: Info = {
|
|
291
|
+
id: "biome",
|
|
292
|
+
root: NearestRoot([
|
|
293
|
+
"biome.json",
|
|
294
|
+
"biome.jsonc",
|
|
295
|
+
"package-lock.json",
|
|
296
|
+
"bun.lockb",
|
|
297
|
+
"bun.lock",
|
|
298
|
+
"pnpm-lock.yaml",
|
|
299
|
+
"yarn.lock",
|
|
300
|
+
]),
|
|
301
|
+
extensions: [
|
|
302
|
+
".ts",
|
|
303
|
+
".tsx",
|
|
304
|
+
".js",
|
|
305
|
+
".jsx",
|
|
306
|
+
".mjs",
|
|
307
|
+
".cjs",
|
|
308
|
+
".mts",
|
|
309
|
+
".cts",
|
|
310
|
+
".json",
|
|
311
|
+
".jsonc",
|
|
312
|
+
".vue",
|
|
313
|
+
".astro",
|
|
314
|
+
".svelte",
|
|
315
|
+
".css",
|
|
316
|
+
".graphql",
|
|
317
|
+
".gql",
|
|
318
|
+
".html",
|
|
319
|
+
],
|
|
320
|
+
async spawn(root) {
|
|
321
|
+
const localBin = path.join(root, "node_modules", ".bin", "biome")
|
|
322
|
+
let bin: string | undefined
|
|
323
|
+
if (await Bun.file(localBin).exists()) bin = localBin
|
|
324
|
+
if (!bin) {
|
|
325
|
+
const found = Bun.which("biome")
|
|
326
|
+
if (found) bin = found
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let args = ["lsp-proxy", "--stdio"]
|
|
330
|
+
|
|
331
|
+
if (!bin) {
|
|
332
|
+
const resolved = await Bun.resolve("biome", root).catch(() => undefined)
|
|
333
|
+
if (!resolved) return
|
|
334
|
+
bin = BunProc.which()
|
|
335
|
+
args = ["x", "biome", "lsp-proxy", "--stdio"]
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const proc = spawn(bin, args, {
|
|
339
|
+
cwd: root,
|
|
340
|
+
env: {
|
|
341
|
+
...process.env,
|
|
342
|
+
BUN_BE_BUN: "1",
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
process: proc,
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export const Gopls: Info = {
|
|
353
|
+
id: "gopls",
|
|
354
|
+
root: async (file) => {
|
|
355
|
+
const work = await NearestRoot(["go.work"])(file)
|
|
356
|
+
if (work) return work
|
|
357
|
+
return NearestRoot(["go.mod", "go.sum"])(file)
|
|
358
|
+
},
|
|
359
|
+
extensions: [".go"],
|
|
360
|
+
async spawn(root) {
|
|
361
|
+
let bin = Bun.which("gopls", {
|
|
362
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
363
|
+
})
|
|
364
|
+
if (!bin) {
|
|
365
|
+
if (!Bun.which("go")) return
|
|
366
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
367
|
+
|
|
368
|
+
log.info("installing gopls")
|
|
369
|
+
const proc = Bun.spawn({
|
|
370
|
+
cmd: ["go", "install", "golang.org/x/tools/gopls@latest"],
|
|
371
|
+
env: { ...process.env, GOBIN: Global.Path.bin },
|
|
372
|
+
stdout: "pipe",
|
|
373
|
+
stderr: "pipe",
|
|
374
|
+
stdin: "pipe",
|
|
375
|
+
})
|
|
376
|
+
const exit = await proc.exited
|
|
377
|
+
if (exit !== 0) {
|
|
378
|
+
log.error("Failed to install gopls")
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
bin = path.join(Global.Path.bin, "gopls" + (process.platform === "win32" ? ".exe" : ""))
|
|
382
|
+
log.info(`installed gopls`, {
|
|
383
|
+
bin,
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
process: spawn(bin!, {
|
|
388
|
+
cwd: root,
|
|
389
|
+
}),
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export const Rubocop: Info = {
|
|
395
|
+
id: "ruby-lsp",
|
|
396
|
+
root: NearestRoot(["Gemfile"]),
|
|
397
|
+
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
|
398
|
+
async spawn(root) {
|
|
399
|
+
let bin = Bun.which("rubocop", {
|
|
400
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
401
|
+
})
|
|
402
|
+
if (!bin) {
|
|
403
|
+
const ruby = Bun.which("ruby")
|
|
404
|
+
const gem = Bun.which("gem")
|
|
405
|
+
if (!ruby || !gem) {
|
|
406
|
+
log.info("Ruby not found, please install Ruby first")
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
410
|
+
log.info("installing rubocop")
|
|
411
|
+
const proc = Bun.spawn({
|
|
412
|
+
cmd: ["gem", "install", "rubocop", "--bindir", Global.Path.bin],
|
|
413
|
+
stdout: "pipe",
|
|
414
|
+
stderr: "pipe",
|
|
415
|
+
stdin: "pipe",
|
|
416
|
+
})
|
|
417
|
+
const exit = await proc.exited
|
|
418
|
+
if (exit !== 0) {
|
|
419
|
+
log.error("Failed to install rubocop")
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : ""))
|
|
423
|
+
log.info(`installed rubocop`, {
|
|
424
|
+
bin,
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
process: spawn(bin!, ["--lsp"], {
|
|
429
|
+
cwd: root,
|
|
430
|
+
}),
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export const Ty: Info = {
|
|
436
|
+
id: "ty",
|
|
437
|
+
extensions: [".py", ".pyi"],
|
|
438
|
+
root: NearestRoot([
|
|
439
|
+
"pyproject.toml",
|
|
440
|
+
"ty.toml",
|
|
441
|
+
"setup.py",
|
|
442
|
+
"setup.cfg",
|
|
443
|
+
"requirements.txt",
|
|
444
|
+
"Pipfile",
|
|
445
|
+
"pyrightconfig.json",
|
|
446
|
+
]),
|
|
447
|
+
async spawn(root) {
|
|
448
|
+
if (!Flag.RIRD_EXPERIMENTAL_LSP_TY) {
|
|
449
|
+
return undefined
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let binary = Bun.which("ty")
|
|
453
|
+
|
|
454
|
+
const initialization: Record<string, string> = {}
|
|
455
|
+
|
|
456
|
+
const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter(
|
|
457
|
+
(p): p is string => p !== undefined,
|
|
458
|
+
)
|
|
459
|
+
for (const venvPath of potentialVenvPaths) {
|
|
460
|
+
const isWindows = process.platform === "win32"
|
|
461
|
+
const potentialPythonPath = isWindows
|
|
462
|
+
? path.join(venvPath, "Scripts", "python.exe")
|
|
463
|
+
: path.join(venvPath, "bin", "python")
|
|
464
|
+
if (await Bun.file(potentialPythonPath).exists()) {
|
|
465
|
+
initialization["pythonPath"] = potentialPythonPath
|
|
466
|
+
break
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!binary) {
|
|
471
|
+
for (const venvPath of potentialVenvPaths) {
|
|
472
|
+
const isWindows = process.platform === "win32"
|
|
473
|
+
const potentialTyPath = isWindows
|
|
474
|
+
? path.join(venvPath, "Scripts", "ty.exe")
|
|
475
|
+
: path.join(venvPath, "bin", "ty")
|
|
476
|
+
if (await Bun.file(potentialTyPath).exists()) {
|
|
477
|
+
binary = potentialTyPath
|
|
478
|
+
break
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (!binary) {
|
|
484
|
+
log.error("ty not found, please install ty first")
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const proc = spawn(binary, ["server"], {
|
|
489
|
+
cwd: root,
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
process: proc,
|
|
494
|
+
initialization,
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export const Pyright: Info = {
|
|
500
|
+
id: "pyright",
|
|
501
|
+
extensions: [".py", ".pyi"],
|
|
502
|
+
root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
|
|
503
|
+
async spawn(root) {
|
|
504
|
+
let binary = Bun.which("pyright-langserver")
|
|
505
|
+
const args = []
|
|
506
|
+
if (!binary) {
|
|
507
|
+
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
|
508
|
+
if (!(await Bun.file(js).exists())) {
|
|
509
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
510
|
+
await Bun.spawn([BunProc.which(), "install", "pyright"], {
|
|
511
|
+
cwd: Global.Path.bin,
|
|
512
|
+
env: {
|
|
513
|
+
...process.env,
|
|
514
|
+
BUN_BE_BUN: "1",
|
|
515
|
+
},
|
|
516
|
+
}).exited
|
|
517
|
+
}
|
|
518
|
+
binary = BunProc.which()
|
|
519
|
+
args.push(...["run", js])
|
|
520
|
+
}
|
|
521
|
+
args.push("--stdio")
|
|
522
|
+
|
|
523
|
+
const initialization: Record<string, string> = {}
|
|
524
|
+
|
|
525
|
+
const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter(
|
|
526
|
+
(p): p is string => p !== undefined,
|
|
527
|
+
)
|
|
528
|
+
for (const venvPath of potentialVenvPaths) {
|
|
529
|
+
const isWindows = process.platform === "win32"
|
|
530
|
+
const potentialPythonPath = isWindows
|
|
531
|
+
? path.join(venvPath, "Scripts", "python.exe")
|
|
532
|
+
: path.join(venvPath, "bin", "python")
|
|
533
|
+
if (await Bun.file(potentialPythonPath).exists()) {
|
|
534
|
+
initialization["pythonPath"] = potentialPythonPath
|
|
535
|
+
break
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const proc = spawn(binary, args, {
|
|
540
|
+
cwd: root,
|
|
541
|
+
env: {
|
|
542
|
+
...process.env,
|
|
543
|
+
BUN_BE_BUN: "1",
|
|
544
|
+
},
|
|
545
|
+
})
|
|
546
|
+
return {
|
|
547
|
+
process: proc,
|
|
548
|
+
initialization,
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export const ElixirLS: Info = {
|
|
554
|
+
id: "elixir-ls",
|
|
555
|
+
extensions: [".ex", ".exs"],
|
|
556
|
+
root: NearestRoot(["mix.exs", "mix.lock"]),
|
|
557
|
+
async spawn(root) {
|
|
558
|
+
let binary = Bun.which("elixir-ls")
|
|
559
|
+
if (!binary) {
|
|
560
|
+
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
|
561
|
+
binary = path.join(
|
|
562
|
+
Global.Path.bin,
|
|
563
|
+
"elixir-ls-master",
|
|
564
|
+
"release",
|
|
565
|
+
process.platform === "win32" ? "language_server.bat" : "language_server.sh",
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
if (!(await Bun.file(binary).exists())) {
|
|
569
|
+
const elixir = Bun.which("elixir")
|
|
570
|
+
if (!elixir) {
|
|
571
|
+
log.error("elixir is required to run elixir-ls")
|
|
572
|
+
return
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
576
|
+
log.info("downloading elixir-ls from GitHub releases")
|
|
577
|
+
|
|
578
|
+
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
|
579
|
+
if (!response.ok) return
|
|
580
|
+
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
|
581
|
+
await Bun.file(zipPath).write(response)
|
|
582
|
+
|
|
583
|
+
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
|
|
584
|
+
.then(() => true)
|
|
585
|
+
.catch((error) => {
|
|
586
|
+
log.error("Failed to extract elixir-ls archive", { error })
|
|
587
|
+
return false
|
|
588
|
+
})
|
|
589
|
+
if (!ok) return
|
|
590
|
+
|
|
591
|
+
await fs.rm(zipPath, {
|
|
592
|
+
force: true,
|
|
593
|
+
recursive: true,
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release`
|
|
597
|
+
.quiet()
|
|
598
|
+
.cwd(path.join(Global.Path.bin, "elixir-ls-master"))
|
|
599
|
+
.env({ MIX_ENV: "prod", ...process.env })
|
|
600
|
+
|
|
601
|
+
log.info(`installed elixir-ls`, {
|
|
602
|
+
path: elixirLsPath,
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
process: spawn(binary, {
|
|
609
|
+
cwd: root,
|
|
610
|
+
}),
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export const Zls: Info = {
|
|
616
|
+
id: "zls",
|
|
617
|
+
extensions: [".zig", ".zon"],
|
|
618
|
+
root: NearestRoot(["build.zig"]),
|
|
619
|
+
async spawn(root) {
|
|
620
|
+
let bin = Bun.which("zls", {
|
|
621
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
if (!bin) {
|
|
625
|
+
const zig = Bun.which("zig")
|
|
626
|
+
if (!zig) {
|
|
627
|
+
log.error("Zig is required to use zls. Please install Zig first.")
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
632
|
+
log.info("downloading zls from GitHub releases")
|
|
633
|
+
|
|
634
|
+
const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
|
|
635
|
+
if (!releaseResponse.ok) {
|
|
636
|
+
log.error("Failed to fetch zls release info")
|
|
637
|
+
return
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const release = (await releaseResponse.json()) as any
|
|
641
|
+
|
|
642
|
+
const platform = process.platform
|
|
643
|
+
const arch = process.arch
|
|
644
|
+
let assetName = ""
|
|
645
|
+
|
|
646
|
+
let zlsArch: string = arch
|
|
647
|
+
if (arch === "arm64") zlsArch = "aarch64"
|
|
648
|
+
else if (arch === "x64") zlsArch = "x86_64"
|
|
649
|
+
else if (arch === "ia32") zlsArch = "x86"
|
|
650
|
+
|
|
651
|
+
let zlsPlatform: string = platform
|
|
652
|
+
if (platform === "darwin") zlsPlatform = "macos"
|
|
653
|
+
else if (platform === "win32") zlsPlatform = "windows"
|
|
654
|
+
|
|
655
|
+
const ext = platform === "win32" ? "zip" : "tar.xz"
|
|
656
|
+
|
|
657
|
+
assetName = `zls-${zlsArch}-${zlsPlatform}.${ext}`
|
|
658
|
+
|
|
659
|
+
const supportedCombos = [
|
|
660
|
+
"zls-x86_64-linux.tar.xz",
|
|
661
|
+
"zls-x86_64-macos.tar.xz",
|
|
662
|
+
"zls-x86_64-windows.zip",
|
|
663
|
+
"zls-aarch64-linux.tar.xz",
|
|
664
|
+
"zls-aarch64-macos.tar.xz",
|
|
665
|
+
"zls-aarch64-windows.zip",
|
|
666
|
+
"zls-x86-linux.tar.xz",
|
|
667
|
+
"zls-x86-windows.zip",
|
|
668
|
+
]
|
|
669
|
+
|
|
670
|
+
if (!supportedCombos.includes(assetName)) {
|
|
671
|
+
log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`)
|
|
672
|
+
return
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const asset = release.assets.find((a: any) => a.name === assetName)
|
|
676
|
+
if (!asset) {
|
|
677
|
+
log.error(`Could not find asset ${assetName} in latest zls release`)
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const downloadUrl = asset.browser_download_url
|
|
682
|
+
const downloadResponse = await fetch(downloadUrl)
|
|
683
|
+
if (!downloadResponse.ok) {
|
|
684
|
+
log.error("Failed to download zls")
|
|
685
|
+
return
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const tempPath = path.join(Global.Path.bin, assetName)
|
|
689
|
+
await Bun.file(tempPath).write(downloadResponse)
|
|
690
|
+
|
|
691
|
+
if (ext === "zip") {
|
|
692
|
+
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
|
693
|
+
.then(() => true)
|
|
694
|
+
.catch((error) => {
|
|
695
|
+
log.error("Failed to extract zls archive", { error })
|
|
696
|
+
return false
|
|
697
|
+
})
|
|
698
|
+
if (!ok) return
|
|
699
|
+
} else {
|
|
700
|
+
await $`tar -xf ${tempPath}`.cwd(Global.Path.bin).quiet().nothrow()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
await fs.rm(tempPath, { force: true })
|
|
704
|
+
|
|
705
|
+
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
|
|
706
|
+
|
|
707
|
+
if (!(await Bun.file(bin).exists())) {
|
|
708
|
+
log.error("Failed to extract zls binary")
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (platform !== "win32") {
|
|
713
|
+
await $`chmod +x ${bin}`.quiet().nothrow()
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
log.info(`installed zls`, { bin })
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
process: spawn(bin, {
|
|
721
|
+
cwd: root,
|
|
722
|
+
}),
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export const CSharp: Info = {
|
|
728
|
+
id: "csharp",
|
|
729
|
+
root: NearestRoot([".sln", ".csproj", "global.json"]),
|
|
730
|
+
extensions: [".cs"],
|
|
731
|
+
async spawn(root) {
|
|
732
|
+
let bin = Bun.which("csharp-ls", {
|
|
733
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
734
|
+
})
|
|
735
|
+
if (!bin) {
|
|
736
|
+
if (!Bun.which("dotnet")) {
|
|
737
|
+
log.error(".NET SDK is required to install csharp-ls")
|
|
738
|
+
return
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
742
|
+
log.info("installing csharp-ls via dotnet tool")
|
|
743
|
+
const proc = Bun.spawn({
|
|
744
|
+
cmd: ["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin],
|
|
745
|
+
stdout: "pipe",
|
|
746
|
+
stderr: "pipe",
|
|
747
|
+
stdin: "pipe",
|
|
748
|
+
})
|
|
749
|
+
const exit = await proc.exited
|
|
750
|
+
if (exit !== 0) {
|
|
751
|
+
log.error("Failed to install csharp-ls")
|
|
752
|
+
return
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
bin = path.join(Global.Path.bin, "csharp-ls" + (process.platform === "win32" ? ".exe" : ""))
|
|
756
|
+
log.info(`installed csharp-ls`, { bin })
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return {
|
|
760
|
+
process: spawn(bin, {
|
|
761
|
+
cwd: root,
|
|
762
|
+
}),
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export const FSharp: Info = {
|
|
768
|
+
id: "fsharp",
|
|
769
|
+
root: NearestRoot([".sln", ".fsproj", "global.json"]),
|
|
770
|
+
extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
|
|
771
|
+
async spawn(root) {
|
|
772
|
+
let bin = Bun.which("fsautocomplete", {
|
|
773
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
774
|
+
})
|
|
775
|
+
if (!bin) {
|
|
776
|
+
if (!Bun.which("dotnet")) {
|
|
777
|
+
log.error(".NET SDK is required to install fsautocomplete")
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
782
|
+
log.info("installing fsautocomplete via dotnet tool")
|
|
783
|
+
const proc = Bun.spawn({
|
|
784
|
+
cmd: ["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin],
|
|
785
|
+
stdout: "pipe",
|
|
786
|
+
stderr: "pipe",
|
|
787
|
+
stdin: "pipe",
|
|
788
|
+
})
|
|
789
|
+
const exit = await proc.exited
|
|
790
|
+
if (exit !== 0) {
|
|
791
|
+
log.error("Failed to install fsautocomplete")
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
bin = path.join(Global.Path.bin, "fsautocomplete" + (process.platform === "win32" ? ".exe" : ""))
|
|
796
|
+
log.info(`installed fsautocomplete`, { bin })
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return {
|
|
800
|
+
process: spawn(bin, {
|
|
801
|
+
cwd: root,
|
|
802
|
+
}),
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export const SourceKit: Info = {
|
|
808
|
+
id: "sourcekit-lsp",
|
|
809
|
+
extensions: [".swift", ".objc", "objcpp"],
|
|
810
|
+
root: NearestRoot(["Package.swift", "*.xcodeproj", "*.xcworkspace"]),
|
|
811
|
+
async spawn(root) {
|
|
812
|
+
// Check if sourcekit-lsp is available in the PATH
|
|
813
|
+
// This is installed with the Swift toolchain
|
|
814
|
+
const sourcekit = Bun.which("sourcekit-lsp")
|
|
815
|
+
if (sourcekit) {
|
|
816
|
+
return {
|
|
817
|
+
process: spawn(sourcekit, {
|
|
818
|
+
cwd: root,
|
|
819
|
+
}),
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// If sourcekit-lsp not found, check if xcrun is available
|
|
824
|
+
// This is specific to macOS where sourcekit-lsp is typically installed with Xcode
|
|
825
|
+
if (!Bun.which("xcrun")) return
|
|
826
|
+
|
|
827
|
+
const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow()
|
|
828
|
+
|
|
829
|
+
if (lspLoc.exitCode !== 0) return
|
|
830
|
+
|
|
831
|
+
const bin = lspLoc.text().trim()
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
process: spawn(bin, {
|
|
835
|
+
cwd: root,
|
|
836
|
+
}),
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
export const RustAnalyzer: Info = {
|
|
842
|
+
id: "rust",
|
|
843
|
+
root: async (root) => {
|
|
844
|
+
const crateRoot = await NearestRoot(["Cargo.toml", "Cargo.lock"])(root)
|
|
845
|
+
if (crateRoot === undefined) {
|
|
846
|
+
return undefined
|
|
847
|
+
}
|
|
848
|
+
let currentDir = crateRoot
|
|
849
|
+
|
|
850
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
851
|
+
// Stop at filesystem root
|
|
852
|
+
const cargoTomlPath = path.join(currentDir, "Cargo.toml")
|
|
853
|
+
try {
|
|
854
|
+
const cargoTomlContent = await Bun.file(cargoTomlPath).text()
|
|
855
|
+
if (cargoTomlContent.includes("[workspace]")) {
|
|
856
|
+
return currentDir
|
|
857
|
+
}
|
|
858
|
+
} catch (err) {
|
|
859
|
+
// File doesn't exist or can't be read, continue searching up
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const parentDir = path.dirname(currentDir)
|
|
863
|
+
if (parentDir === currentDir) break // Reached filesystem root
|
|
864
|
+
currentDir = parentDir
|
|
865
|
+
|
|
866
|
+
// Stop if we've gone above the app root
|
|
867
|
+
if (!currentDir.startsWith(Instance.worktree)) break
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return crateRoot
|
|
871
|
+
},
|
|
872
|
+
extensions: [".rs"],
|
|
873
|
+
async spawn(root) {
|
|
874
|
+
const bin = Bun.which("rust-analyzer")
|
|
875
|
+
if (!bin) {
|
|
876
|
+
log.info("rust-analyzer not found in path, please install it")
|
|
877
|
+
return
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
process: spawn(bin, {
|
|
881
|
+
cwd: root,
|
|
882
|
+
}),
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export const Clangd: Info = {
|
|
888
|
+
id: "clangd",
|
|
889
|
+
root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd", "CMakeLists.txt", "Makefile"]),
|
|
890
|
+
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
|
891
|
+
async spawn(root) {
|
|
892
|
+
const args = ["--background-index", "--clang-tidy"]
|
|
893
|
+
const fromPath = Bun.which("clangd")
|
|
894
|
+
if (fromPath) {
|
|
895
|
+
return {
|
|
896
|
+
process: spawn(fromPath, args, {
|
|
897
|
+
cwd: root,
|
|
898
|
+
}),
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const ext = process.platform === "win32" ? ".exe" : ""
|
|
903
|
+
const direct = path.join(Global.Path.bin, "clangd" + ext)
|
|
904
|
+
if (await Bun.file(direct).exists()) {
|
|
905
|
+
return {
|
|
906
|
+
process: spawn(direct, args, {
|
|
907
|
+
cwd: root,
|
|
908
|
+
}),
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const entries = await fs.readdir(Global.Path.bin, { withFileTypes: true }).catch(() => [])
|
|
913
|
+
for (const entry of entries) {
|
|
914
|
+
if (!entry.isDirectory()) continue
|
|
915
|
+
if (!entry.name.startsWith("clangd_")) continue
|
|
916
|
+
const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
|
|
917
|
+
if (await Bun.file(candidate).exists()) {
|
|
918
|
+
return {
|
|
919
|
+
process: spawn(candidate, args, {
|
|
920
|
+
cwd: root,
|
|
921
|
+
}),
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
927
|
+
log.info("downloading clangd from GitHub releases")
|
|
928
|
+
|
|
929
|
+
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
|
930
|
+
if (!releaseResponse.ok) {
|
|
931
|
+
log.error("Failed to fetch clangd release info")
|
|
932
|
+
return
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const release: {
|
|
936
|
+
tag_name?: string
|
|
937
|
+
assets?: { name?: string; browser_download_url?: string }[]
|
|
938
|
+
} = await releaseResponse.json()
|
|
939
|
+
|
|
940
|
+
const tag = release.tag_name
|
|
941
|
+
if (!tag) {
|
|
942
|
+
log.error("clangd release did not include a tag name")
|
|
943
|
+
return
|
|
944
|
+
}
|
|
945
|
+
const platform = process.platform
|
|
946
|
+
const tokens: Record<string, string> = {
|
|
947
|
+
darwin: "mac",
|
|
948
|
+
linux: "linux",
|
|
949
|
+
win32: "windows",
|
|
950
|
+
}
|
|
951
|
+
const token = tokens[platform]
|
|
952
|
+
if (!token) {
|
|
953
|
+
log.error(`Platform ${platform} is not supported by clangd auto-download`)
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const assets = release.assets ?? []
|
|
958
|
+
const valid = (item: { name?: string; browser_download_url?: string }) => {
|
|
959
|
+
if (!item.name) return false
|
|
960
|
+
if (!item.browser_download_url) return false
|
|
961
|
+
if (!item.name.includes(token)) return false
|
|
962
|
+
return item.name.includes(tag)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const asset =
|
|
966
|
+
assets.find((item) => valid(item) && item.name?.endsWith(".zip")) ??
|
|
967
|
+
assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
|
|
968
|
+
assets.find((item) => valid(item))
|
|
969
|
+
if (!asset?.name || !asset.browser_download_url) {
|
|
970
|
+
log.error("clangd could not match release asset", { tag, platform })
|
|
971
|
+
return
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const name = asset.name
|
|
975
|
+
const downloadResponse = await fetch(asset.browser_download_url)
|
|
976
|
+
if (!downloadResponse.ok) {
|
|
977
|
+
log.error("Failed to download clangd")
|
|
978
|
+
return
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const archive = path.join(Global.Path.bin, name)
|
|
982
|
+
const buf = await downloadResponse.arrayBuffer()
|
|
983
|
+
if (buf.byteLength === 0) {
|
|
984
|
+
log.error("Failed to write clangd archive")
|
|
985
|
+
return
|
|
986
|
+
}
|
|
987
|
+
await Bun.write(archive, buf)
|
|
988
|
+
|
|
989
|
+
const zip = name.endsWith(".zip")
|
|
990
|
+
const tar = name.endsWith(".tar.xz")
|
|
991
|
+
if (!zip && !tar) {
|
|
992
|
+
log.error("clangd encountered unsupported asset", { asset: name })
|
|
993
|
+
return
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (zip) {
|
|
997
|
+
const ok = await Archive.extractZip(archive, Global.Path.bin)
|
|
998
|
+
.then(() => true)
|
|
999
|
+
.catch((error) => {
|
|
1000
|
+
log.error("Failed to extract clangd archive", { error })
|
|
1001
|
+
return false
|
|
1002
|
+
})
|
|
1003
|
+
if (!ok) return
|
|
1004
|
+
}
|
|
1005
|
+
if (tar) {
|
|
1006
|
+
await $`tar -xf ${archive}`.cwd(Global.Path.bin).quiet().nothrow()
|
|
1007
|
+
}
|
|
1008
|
+
await fs.rm(archive, { force: true })
|
|
1009
|
+
|
|
1010
|
+
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
|
|
1011
|
+
if (!(await Bun.file(bin).exists())) {
|
|
1012
|
+
log.error("Failed to extract clangd binary")
|
|
1013
|
+
return
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (platform !== "win32") {
|
|
1017
|
+
await $`chmod +x ${bin}`.quiet().nothrow()
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
|
|
1021
|
+
await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
|
|
1022
|
+
|
|
1023
|
+
log.info(`installed clangd`, { bin })
|
|
1024
|
+
|
|
1025
|
+
return {
|
|
1026
|
+
process: spawn(bin, args, {
|
|
1027
|
+
cwd: root,
|
|
1028
|
+
}),
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
export const Svelte: Info = {
|
|
1034
|
+
id: "svelte",
|
|
1035
|
+
extensions: [".svelte"],
|
|
1036
|
+
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
|
1037
|
+
async spawn(root) {
|
|
1038
|
+
let binary = Bun.which("svelteserver")
|
|
1039
|
+
const args: string[] = []
|
|
1040
|
+
if (!binary) {
|
|
1041
|
+
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
|
|
1042
|
+
if (!(await Bun.file(js).exists())) {
|
|
1043
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1044
|
+
await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
|
|
1045
|
+
cwd: Global.Path.bin,
|
|
1046
|
+
env: {
|
|
1047
|
+
...process.env,
|
|
1048
|
+
BUN_BE_BUN: "1",
|
|
1049
|
+
},
|
|
1050
|
+
stdout: "pipe",
|
|
1051
|
+
stderr: "pipe",
|
|
1052
|
+
stdin: "pipe",
|
|
1053
|
+
}).exited
|
|
1054
|
+
}
|
|
1055
|
+
binary = BunProc.which()
|
|
1056
|
+
args.push("run", js)
|
|
1057
|
+
}
|
|
1058
|
+
args.push("--stdio")
|
|
1059
|
+
const proc = spawn(binary, args, {
|
|
1060
|
+
cwd: root,
|
|
1061
|
+
env: {
|
|
1062
|
+
...process.env,
|
|
1063
|
+
BUN_BE_BUN: "1",
|
|
1064
|
+
},
|
|
1065
|
+
})
|
|
1066
|
+
return {
|
|
1067
|
+
process: proc,
|
|
1068
|
+
initialization: {},
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
export const Astro: Info = {
|
|
1074
|
+
id: "astro",
|
|
1075
|
+
extensions: [".astro"],
|
|
1076
|
+
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
|
1077
|
+
async spawn(root) {
|
|
1078
|
+
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {})
|
|
1079
|
+
if (!tsserver) {
|
|
1080
|
+
log.info("typescript not found, required for Astro language server")
|
|
1081
|
+
return
|
|
1082
|
+
}
|
|
1083
|
+
const tsdk = path.dirname(tsserver)
|
|
1084
|
+
|
|
1085
|
+
let binary = Bun.which("astro-ls")
|
|
1086
|
+
const args: string[] = []
|
|
1087
|
+
if (!binary) {
|
|
1088
|
+
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
|
|
1089
|
+
if (!(await Bun.file(js).exists())) {
|
|
1090
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1091
|
+
await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
|
|
1092
|
+
cwd: Global.Path.bin,
|
|
1093
|
+
env: {
|
|
1094
|
+
...process.env,
|
|
1095
|
+
BUN_BE_BUN: "1",
|
|
1096
|
+
},
|
|
1097
|
+
stdout: "pipe",
|
|
1098
|
+
stderr: "pipe",
|
|
1099
|
+
stdin: "pipe",
|
|
1100
|
+
}).exited
|
|
1101
|
+
}
|
|
1102
|
+
binary = BunProc.which()
|
|
1103
|
+
args.push("run", js)
|
|
1104
|
+
}
|
|
1105
|
+
args.push("--stdio")
|
|
1106
|
+
const proc = spawn(binary, args, {
|
|
1107
|
+
cwd: root,
|
|
1108
|
+
env: {
|
|
1109
|
+
...process.env,
|
|
1110
|
+
BUN_BE_BUN: "1",
|
|
1111
|
+
},
|
|
1112
|
+
})
|
|
1113
|
+
return {
|
|
1114
|
+
process: proc,
|
|
1115
|
+
initialization: {
|
|
1116
|
+
typescript: {
|
|
1117
|
+
tsdk,
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export const JDTLS: Info = {
|
|
1125
|
+
id: "jdtls",
|
|
1126
|
+
root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]),
|
|
1127
|
+
extensions: [".java"],
|
|
1128
|
+
async spawn(root) {
|
|
1129
|
+
const java = Bun.which("java")
|
|
1130
|
+
if (!java) {
|
|
1131
|
+
log.error("Java 21 or newer is required to run the JDTLS. Please install it first.")
|
|
1132
|
+
return
|
|
1133
|
+
}
|
|
1134
|
+
const javaMajorVersion = await $`java -version`
|
|
1135
|
+
.quiet()
|
|
1136
|
+
.nothrow()
|
|
1137
|
+
.then(({ stderr }) => {
|
|
1138
|
+
const m = /"(\d+)\.\d+\.\d+"/.exec(stderr.toString())
|
|
1139
|
+
return !m ? undefined : parseInt(m[1])
|
|
1140
|
+
})
|
|
1141
|
+
if (javaMajorVersion == null || javaMajorVersion < 21) {
|
|
1142
|
+
log.error("JDTLS requires at least Java 21.")
|
|
1143
|
+
return
|
|
1144
|
+
}
|
|
1145
|
+
const distPath = path.join(Global.Path.bin, "jdtls")
|
|
1146
|
+
const launcherDir = path.join(distPath, "plugins")
|
|
1147
|
+
const installed = await fs.exists(launcherDir)
|
|
1148
|
+
if (!installed) {
|
|
1149
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1150
|
+
log.info("Downloading JDTLS LSP server.")
|
|
1151
|
+
await fs.mkdir(distPath, { recursive: true })
|
|
1152
|
+
const releaseURL =
|
|
1153
|
+
"https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz"
|
|
1154
|
+
const archivePath = path.join(distPath, "release.tar.gz")
|
|
1155
|
+
await $`curl -L -o '${archivePath}' '${releaseURL}'`.quiet().nothrow()
|
|
1156
|
+
await $`tar -xzf ${archivePath}`.cwd(distPath).quiet().nothrow()
|
|
1157
|
+
await fs.rm(archivePath, { force: true })
|
|
1158
|
+
}
|
|
1159
|
+
const jarFileName = await $`ls org.eclipse.equinox.launcher_*.jar`
|
|
1160
|
+
.cwd(launcherDir)
|
|
1161
|
+
.quiet()
|
|
1162
|
+
.nothrow()
|
|
1163
|
+
.then(({ stdout }) => stdout.toString().trim())
|
|
1164
|
+
const launcherJar = path.join(launcherDir, jarFileName)
|
|
1165
|
+
if (!(await fs.exists(launcherJar))) {
|
|
1166
|
+
log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`)
|
|
1167
|
+
return
|
|
1168
|
+
}
|
|
1169
|
+
const configFile = path.join(
|
|
1170
|
+
distPath,
|
|
1171
|
+
(() => {
|
|
1172
|
+
switch (process.platform) {
|
|
1173
|
+
case "darwin":
|
|
1174
|
+
return "config_mac"
|
|
1175
|
+
case "linux":
|
|
1176
|
+
return "config_linux"
|
|
1177
|
+
case "win32":
|
|
1178
|
+
return "config_windows"
|
|
1179
|
+
default:
|
|
1180
|
+
return "config_linux"
|
|
1181
|
+
}
|
|
1182
|
+
})(),
|
|
1183
|
+
)
|
|
1184
|
+
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data"))
|
|
1185
|
+
return {
|
|
1186
|
+
process: spawn(
|
|
1187
|
+
java,
|
|
1188
|
+
[
|
|
1189
|
+
"-jar",
|
|
1190
|
+
launcherJar,
|
|
1191
|
+
"-configuration",
|
|
1192
|
+
configFile,
|
|
1193
|
+
"-data",
|
|
1194
|
+
dataDir,
|
|
1195
|
+
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
|
|
1196
|
+
"-Dosgi.bundles.defaultStartLevel=4",
|
|
1197
|
+
"-Declipse.product=org.eclipse.jdt.ls.core.product",
|
|
1198
|
+
"-Dlog.level=ALL",
|
|
1199
|
+
"--add-modules=ALL-SYSTEM",
|
|
1200
|
+
"--add-opens java.base/java.util=ALL-UNNAMED",
|
|
1201
|
+
"--add-opens java.base/java.lang=ALL-UNNAMED",
|
|
1202
|
+
],
|
|
1203
|
+
{
|
|
1204
|
+
cwd: root,
|
|
1205
|
+
},
|
|
1206
|
+
),
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export const YamlLS: Info = {
|
|
1212
|
+
id: "yaml-ls",
|
|
1213
|
+
extensions: [".yaml", ".yml"],
|
|
1214
|
+
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
|
1215
|
+
async spawn(root) {
|
|
1216
|
+
let binary = Bun.which("yaml-language-server")
|
|
1217
|
+
const args: string[] = []
|
|
1218
|
+
if (!binary) {
|
|
1219
|
+
const js = path.join(
|
|
1220
|
+
Global.Path.bin,
|
|
1221
|
+
"node_modules",
|
|
1222
|
+
"yaml-language-server",
|
|
1223
|
+
"out",
|
|
1224
|
+
"server",
|
|
1225
|
+
"src",
|
|
1226
|
+
"server.js",
|
|
1227
|
+
)
|
|
1228
|
+
const exists = await Bun.file(js).exists()
|
|
1229
|
+
if (!exists) {
|
|
1230
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1231
|
+
await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], {
|
|
1232
|
+
cwd: Global.Path.bin,
|
|
1233
|
+
env: {
|
|
1234
|
+
...process.env,
|
|
1235
|
+
BUN_BE_BUN: "1",
|
|
1236
|
+
},
|
|
1237
|
+
stdout: "pipe",
|
|
1238
|
+
stderr: "pipe",
|
|
1239
|
+
stdin: "pipe",
|
|
1240
|
+
}).exited
|
|
1241
|
+
}
|
|
1242
|
+
binary = BunProc.which()
|
|
1243
|
+
args.push("run", js)
|
|
1244
|
+
}
|
|
1245
|
+
args.push("--stdio")
|
|
1246
|
+
const proc = spawn(binary, args, {
|
|
1247
|
+
cwd: root,
|
|
1248
|
+
env: {
|
|
1249
|
+
...process.env,
|
|
1250
|
+
BUN_BE_BUN: "1",
|
|
1251
|
+
},
|
|
1252
|
+
})
|
|
1253
|
+
return {
|
|
1254
|
+
process: proc,
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
export const LuaLS: Info = {
|
|
1260
|
+
id: "lua-ls",
|
|
1261
|
+
root: NearestRoot([
|
|
1262
|
+
".luarc.json",
|
|
1263
|
+
".luarc.jsonc",
|
|
1264
|
+
".luacheckrc",
|
|
1265
|
+
".stylua.toml",
|
|
1266
|
+
"stylua.toml",
|
|
1267
|
+
"selene.toml",
|
|
1268
|
+
"selene.yml",
|
|
1269
|
+
]),
|
|
1270
|
+
extensions: [".lua"],
|
|
1271
|
+
async spawn(root) {
|
|
1272
|
+
let bin = Bun.which("lua-language-server", {
|
|
1273
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
1274
|
+
})
|
|
1275
|
+
|
|
1276
|
+
if (!bin) {
|
|
1277
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1278
|
+
log.info("downloading lua-language-server from GitHub releases")
|
|
1279
|
+
|
|
1280
|
+
const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest")
|
|
1281
|
+
if (!releaseResponse.ok) {
|
|
1282
|
+
log.error("Failed to fetch lua-language-server release info")
|
|
1283
|
+
return
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const release = await releaseResponse.json()
|
|
1287
|
+
|
|
1288
|
+
const platform = process.platform
|
|
1289
|
+
const arch = process.arch
|
|
1290
|
+
let assetName = ""
|
|
1291
|
+
|
|
1292
|
+
let lualsArch: string = arch
|
|
1293
|
+
if (arch === "arm64") lualsArch = "arm64"
|
|
1294
|
+
else if (arch === "x64") lualsArch = "x64"
|
|
1295
|
+
else if (arch === "ia32") lualsArch = "ia32"
|
|
1296
|
+
|
|
1297
|
+
let lualsPlatform: string = platform
|
|
1298
|
+
if (platform === "darwin") lualsPlatform = "darwin"
|
|
1299
|
+
else if (platform === "linux") lualsPlatform = "linux"
|
|
1300
|
+
else if (platform === "win32") lualsPlatform = "win32"
|
|
1301
|
+
|
|
1302
|
+
const ext = platform === "win32" ? "zip" : "tar.gz"
|
|
1303
|
+
|
|
1304
|
+
assetName = `lua-language-server-${release.tag_name}-${lualsPlatform}-${lualsArch}.${ext}`
|
|
1305
|
+
|
|
1306
|
+
const supportedCombos = [
|
|
1307
|
+
"darwin-arm64.tar.gz",
|
|
1308
|
+
"darwin-x64.tar.gz",
|
|
1309
|
+
"linux-x64.tar.gz",
|
|
1310
|
+
"linux-arm64.tar.gz",
|
|
1311
|
+
"win32-x64.zip",
|
|
1312
|
+
"win32-ia32.zip",
|
|
1313
|
+
]
|
|
1314
|
+
|
|
1315
|
+
const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}`
|
|
1316
|
+
if (!supportedCombos.includes(assetSuffix)) {
|
|
1317
|
+
log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`)
|
|
1318
|
+
return
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const asset = release.assets.find((a: any) => a.name === assetName)
|
|
1322
|
+
if (!asset) {
|
|
1323
|
+
log.error(`Could not find asset ${assetName} in latest lua-language-server release`)
|
|
1324
|
+
return
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const downloadUrl = asset.browser_download_url
|
|
1328
|
+
const downloadResponse = await fetch(downloadUrl)
|
|
1329
|
+
if (!downloadResponse.ok) {
|
|
1330
|
+
log.error("Failed to download lua-language-server")
|
|
1331
|
+
return
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const tempPath = path.join(Global.Path.bin, assetName)
|
|
1335
|
+
await Bun.file(tempPath).write(downloadResponse)
|
|
1336
|
+
|
|
1337
|
+
// Unlike zls which is a single self-contained binary,
|
|
1338
|
+
// lua-language-server needs supporting files (meta/, locale/, etc.)
|
|
1339
|
+
// Extract entire archive to dedicated directory to preserve all files
|
|
1340
|
+
const installDir = path.join(Global.Path.bin, `lua-language-server-${lualsArch}-${lualsPlatform}`)
|
|
1341
|
+
|
|
1342
|
+
// Remove old installation if exists
|
|
1343
|
+
const stats = await fs.stat(installDir).catch(() => undefined)
|
|
1344
|
+
if (stats) {
|
|
1345
|
+
await fs.rm(installDir, { force: true, recursive: true })
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
await fs.mkdir(installDir, { recursive: true })
|
|
1349
|
+
|
|
1350
|
+
if (ext === "zip") {
|
|
1351
|
+
const ok = await Archive.extractZip(tempPath, installDir)
|
|
1352
|
+
.then(() => true)
|
|
1353
|
+
.catch((error) => {
|
|
1354
|
+
log.error("Failed to extract lua-language-server archive", { error })
|
|
1355
|
+
return false
|
|
1356
|
+
})
|
|
1357
|
+
if (!ok) return
|
|
1358
|
+
} else {
|
|
1359
|
+
const ok = await $`tar -xzf ${tempPath} -C ${installDir}`
|
|
1360
|
+
.quiet()
|
|
1361
|
+
.then(() => true)
|
|
1362
|
+
.catch((error) => {
|
|
1363
|
+
log.error("Failed to extract lua-language-server archive", { error })
|
|
1364
|
+
return false
|
|
1365
|
+
})
|
|
1366
|
+
if (!ok) return
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
await fs.rm(tempPath, { force: true })
|
|
1370
|
+
|
|
1371
|
+
// Binary is located in bin/ subdirectory within the extracted archive
|
|
1372
|
+
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
|
|
1373
|
+
|
|
1374
|
+
if (!(await Bun.file(bin).exists())) {
|
|
1375
|
+
log.error("Failed to extract lua-language-server binary")
|
|
1376
|
+
return
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (platform !== "win32") {
|
|
1380
|
+
const ok = await $`chmod +x ${bin}`.quiet().catch((error) => {
|
|
1381
|
+
log.error("Failed to set executable permission for lua-language-server binary", {
|
|
1382
|
+
error,
|
|
1383
|
+
})
|
|
1384
|
+
})
|
|
1385
|
+
if (!ok) return
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
log.info(`installed lua-language-server`, { bin })
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
return {
|
|
1392
|
+
process: spawn(bin, {
|
|
1393
|
+
cwd: root,
|
|
1394
|
+
}),
|
|
1395
|
+
}
|
|
1396
|
+
},
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
export const PHPIntelephense: Info = {
|
|
1400
|
+
id: "php intelephense",
|
|
1401
|
+
extensions: [".php"],
|
|
1402
|
+
root: NearestRoot(["composer.json", "composer.lock", ".php-version"]),
|
|
1403
|
+
async spawn(root) {
|
|
1404
|
+
let binary = Bun.which("intelephense")
|
|
1405
|
+
const args: string[] = []
|
|
1406
|
+
if (!binary) {
|
|
1407
|
+
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
|
|
1408
|
+
if (!(await Bun.file(js).exists())) {
|
|
1409
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1410
|
+
await Bun.spawn([BunProc.which(), "install", "intelephense"], {
|
|
1411
|
+
cwd: Global.Path.bin,
|
|
1412
|
+
env: {
|
|
1413
|
+
...process.env,
|
|
1414
|
+
BUN_BE_BUN: "1",
|
|
1415
|
+
},
|
|
1416
|
+
stdout: "pipe",
|
|
1417
|
+
stderr: "pipe",
|
|
1418
|
+
stdin: "pipe",
|
|
1419
|
+
}).exited
|
|
1420
|
+
}
|
|
1421
|
+
binary = BunProc.which()
|
|
1422
|
+
args.push("run", js)
|
|
1423
|
+
}
|
|
1424
|
+
args.push("--stdio")
|
|
1425
|
+
const proc = spawn(binary, args, {
|
|
1426
|
+
cwd: root,
|
|
1427
|
+
env: {
|
|
1428
|
+
...process.env,
|
|
1429
|
+
BUN_BE_BUN: "1",
|
|
1430
|
+
},
|
|
1431
|
+
})
|
|
1432
|
+
return {
|
|
1433
|
+
process: proc,
|
|
1434
|
+
initialization: {},
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
export const Dart: Info = {
|
|
1440
|
+
id: "dart",
|
|
1441
|
+
extensions: [".dart"],
|
|
1442
|
+
root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"]),
|
|
1443
|
+
async spawn(root) {
|
|
1444
|
+
const dart = Bun.which("dart")
|
|
1445
|
+
if (!dart) {
|
|
1446
|
+
log.info("dart not found, please install dart first")
|
|
1447
|
+
return
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
1450
|
+
process: spawn(dart, ["language-server", "--lsp"], {
|
|
1451
|
+
cwd: root,
|
|
1452
|
+
}),
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
export const Ocaml: Info = {
|
|
1458
|
+
id: "ocaml-lsp",
|
|
1459
|
+
extensions: [".ml", ".mli"],
|
|
1460
|
+
root: NearestRoot(["dune-project", "dune-workspace", ".merlin", "opam"]),
|
|
1461
|
+
async spawn(root) {
|
|
1462
|
+
const bin = Bun.which("ocamllsp")
|
|
1463
|
+
if (!bin) {
|
|
1464
|
+
log.info("ocamllsp not found, please install ocaml-lsp-server")
|
|
1465
|
+
return
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
process: spawn(bin, {
|
|
1469
|
+
cwd: root,
|
|
1470
|
+
}),
|
|
1471
|
+
}
|
|
1472
|
+
},
|
|
1473
|
+
}
|
|
1474
|
+
export const BashLS: Info = {
|
|
1475
|
+
id: "bash",
|
|
1476
|
+
extensions: [".sh", ".bash", ".zsh", ".ksh"],
|
|
1477
|
+
root: async () => Instance.directory,
|
|
1478
|
+
async spawn(root) {
|
|
1479
|
+
let binary = Bun.which("bash-language-server")
|
|
1480
|
+
const args: string[] = []
|
|
1481
|
+
if (!binary) {
|
|
1482
|
+
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
|
|
1483
|
+
if (!(await Bun.file(js).exists())) {
|
|
1484
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1485
|
+
await Bun.spawn([BunProc.which(), "install", "bash-language-server"], {
|
|
1486
|
+
cwd: Global.Path.bin,
|
|
1487
|
+
env: {
|
|
1488
|
+
...process.env,
|
|
1489
|
+
BUN_BE_BUN: "1",
|
|
1490
|
+
},
|
|
1491
|
+
stdout: "pipe",
|
|
1492
|
+
stderr: "pipe",
|
|
1493
|
+
stdin: "pipe",
|
|
1494
|
+
}).exited
|
|
1495
|
+
}
|
|
1496
|
+
binary = BunProc.which()
|
|
1497
|
+
args.push("run", js)
|
|
1498
|
+
}
|
|
1499
|
+
args.push("start")
|
|
1500
|
+
const proc = spawn(binary, args, {
|
|
1501
|
+
cwd: root,
|
|
1502
|
+
env: {
|
|
1503
|
+
...process.env,
|
|
1504
|
+
BUN_BE_BUN: "1",
|
|
1505
|
+
},
|
|
1506
|
+
})
|
|
1507
|
+
return {
|
|
1508
|
+
process: proc,
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
export const TerraformLS: Info = {
|
|
1514
|
+
id: "terraform",
|
|
1515
|
+
extensions: [".tf", ".tfvars"],
|
|
1516
|
+
root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
|
|
1517
|
+
async spawn(root) {
|
|
1518
|
+
let bin = Bun.which("terraform-ls", {
|
|
1519
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
1520
|
+
})
|
|
1521
|
+
|
|
1522
|
+
if (!bin) {
|
|
1523
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1524
|
+
log.info("downloading terraform-ls from GitHub releases")
|
|
1525
|
+
|
|
1526
|
+
const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest")
|
|
1527
|
+
if (!releaseResponse.ok) {
|
|
1528
|
+
log.error("Failed to fetch terraform-ls release info")
|
|
1529
|
+
return
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const release = (await releaseResponse.json()) as {
|
|
1533
|
+
tag_name?: string
|
|
1534
|
+
assets?: { name?: string; browser_download_url?: string }[]
|
|
1535
|
+
}
|
|
1536
|
+
const version = release.tag_name?.replace("v", "")
|
|
1537
|
+
if (!version) {
|
|
1538
|
+
log.error("terraform-ls release did not include a version tag")
|
|
1539
|
+
return
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
const platform = process.platform
|
|
1543
|
+
const arch = process.arch
|
|
1544
|
+
|
|
1545
|
+
const tfArch = arch === "arm64" ? "arm64" : "amd64"
|
|
1546
|
+
const tfPlatform = platform === "win32" ? "windows" : platform
|
|
1547
|
+
|
|
1548
|
+
const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip`
|
|
1549
|
+
|
|
1550
|
+
const assets = release.assets ?? []
|
|
1551
|
+
const asset = assets.find((a) => a.name === assetName)
|
|
1552
|
+
if (!asset?.browser_download_url) {
|
|
1553
|
+
log.error(`Could not find asset ${assetName} in terraform-ls release`)
|
|
1554
|
+
return
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
const downloadResponse = await fetch(asset.browser_download_url)
|
|
1558
|
+
if (!downloadResponse.ok) {
|
|
1559
|
+
log.error("Failed to download terraform-ls")
|
|
1560
|
+
return
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const tempPath = path.join(Global.Path.bin, assetName)
|
|
1564
|
+
await Bun.file(tempPath).write(downloadResponse)
|
|
1565
|
+
|
|
1566
|
+
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
|
1567
|
+
.then(() => true)
|
|
1568
|
+
.catch((error) => {
|
|
1569
|
+
log.error("Failed to extract terraform-ls archive", { error })
|
|
1570
|
+
return false
|
|
1571
|
+
})
|
|
1572
|
+
if (!ok) return
|
|
1573
|
+
await fs.rm(tempPath, { force: true })
|
|
1574
|
+
|
|
1575
|
+
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
|
|
1576
|
+
|
|
1577
|
+
if (!(await Bun.file(bin).exists())) {
|
|
1578
|
+
log.error("Failed to extract terraform-ls binary")
|
|
1579
|
+
return
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
if (platform !== "win32") {
|
|
1583
|
+
await $`chmod +x ${bin}`.quiet().nothrow()
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
log.info(`installed terraform-ls`, { bin })
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
return {
|
|
1590
|
+
process: spawn(bin, ["serve"], {
|
|
1591
|
+
cwd: root,
|
|
1592
|
+
}),
|
|
1593
|
+
initialization: {
|
|
1594
|
+
experimentalFeatures: {
|
|
1595
|
+
prefillRequiredFields: true,
|
|
1596
|
+
validateOnSave: true,
|
|
1597
|
+
},
|
|
1598
|
+
},
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
export const TexLab: Info = {
|
|
1604
|
+
id: "texlab",
|
|
1605
|
+
extensions: [".tex", ".bib"],
|
|
1606
|
+
root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]),
|
|
1607
|
+
async spawn(root) {
|
|
1608
|
+
let bin = Bun.which("texlab", {
|
|
1609
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
1610
|
+
})
|
|
1611
|
+
|
|
1612
|
+
if (!bin) {
|
|
1613
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1614
|
+
log.info("downloading texlab from GitHub releases")
|
|
1615
|
+
|
|
1616
|
+
const response = await fetch("https://api.github.com/repos/latex-lsp/texlab/releases/latest")
|
|
1617
|
+
if (!response.ok) {
|
|
1618
|
+
log.error("Failed to fetch texlab release info")
|
|
1619
|
+
return
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const release = (await response.json()) as {
|
|
1623
|
+
tag_name?: string
|
|
1624
|
+
assets?: { name?: string; browser_download_url?: string }[]
|
|
1625
|
+
}
|
|
1626
|
+
const version = release.tag_name?.replace("v", "")
|
|
1627
|
+
if (!version) {
|
|
1628
|
+
log.error("texlab release did not include a version tag")
|
|
1629
|
+
return
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const platform = process.platform
|
|
1633
|
+
const arch = process.arch
|
|
1634
|
+
|
|
1635
|
+
const texArch = arch === "arm64" ? "aarch64" : "x86_64"
|
|
1636
|
+
const texPlatform = platform === "darwin" ? "macos" : platform === "win32" ? "windows" : "linux"
|
|
1637
|
+
const ext = platform === "win32" ? "zip" : "tar.gz"
|
|
1638
|
+
const assetName = `texlab-${texArch}-${texPlatform}.${ext}`
|
|
1639
|
+
|
|
1640
|
+
const assets = release.assets ?? []
|
|
1641
|
+
const asset = assets.find((a) => a.name === assetName)
|
|
1642
|
+
if (!asset?.browser_download_url) {
|
|
1643
|
+
log.error(`Could not find asset ${assetName} in texlab release`)
|
|
1644
|
+
return
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
const downloadResponse = await fetch(asset.browser_download_url)
|
|
1648
|
+
if (!downloadResponse.ok) {
|
|
1649
|
+
log.error("Failed to download texlab")
|
|
1650
|
+
return
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const tempPath = path.join(Global.Path.bin, assetName)
|
|
1654
|
+
await Bun.file(tempPath).write(downloadResponse)
|
|
1655
|
+
|
|
1656
|
+
if (ext === "zip") {
|
|
1657
|
+
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
|
1658
|
+
.then(() => true)
|
|
1659
|
+
.catch((error) => {
|
|
1660
|
+
log.error("Failed to extract texlab archive", { error })
|
|
1661
|
+
return false
|
|
1662
|
+
})
|
|
1663
|
+
if (!ok) return
|
|
1664
|
+
}
|
|
1665
|
+
if (ext === "tar.gz") {
|
|
1666
|
+
await $`tar -xzf ${tempPath}`.cwd(Global.Path.bin).quiet().nothrow()
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
await fs.rm(tempPath, { force: true })
|
|
1670
|
+
|
|
1671
|
+
bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
|
|
1672
|
+
|
|
1673
|
+
if (!(await Bun.file(bin).exists())) {
|
|
1674
|
+
log.error("Failed to extract texlab binary")
|
|
1675
|
+
return
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (platform !== "win32") {
|
|
1679
|
+
await $`chmod +x ${bin}`.quiet().nothrow()
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
log.info("installed texlab", { bin })
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
return {
|
|
1686
|
+
process: spawn(bin, {
|
|
1687
|
+
cwd: root,
|
|
1688
|
+
}),
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
export const DockerfileLS: Info = {
|
|
1694
|
+
id: "dockerfile",
|
|
1695
|
+
extensions: [".dockerfile", "Dockerfile"],
|
|
1696
|
+
root: async () => Instance.directory,
|
|
1697
|
+
async spawn(root) {
|
|
1698
|
+
let binary = Bun.which("docker-langserver")
|
|
1699
|
+
const args: string[] = []
|
|
1700
|
+
if (!binary) {
|
|
1701
|
+
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
|
|
1702
|
+
if (!(await Bun.file(js).exists())) {
|
|
1703
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1704
|
+
await Bun.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], {
|
|
1705
|
+
cwd: Global.Path.bin,
|
|
1706
|
+
env: {
|
|
1707
|
+
...process.env,
|
|
1708
|
+
BUN_BE_BUN: "1",
|
|
1709
|
+
},
|
|
1710
|
+
stdout: "pipe",
|
|
1711
|
+
stderr: "pipe",
|
|
1712
|
+
stdin: "pipe",
|
|
1713
|
+
}).exited
|
|
1714
|
+
}
|
|
1715
|
+
binary = BunProc.which()
|
|
1716
|
+
args.push("run", js)
|
|
1717
|
+
}
|
|
1718
|
+
args.push("--stdio")
|
|
1719
|
+
const proc = spawn(binary, args, {
|
|
1720
|
+
cwd: root,
|
|
1721
|
+
env: {
|
|
1722
|
+
...process.env,
|
|
1723
|
+
BUN_BE_BUN: "1",
|
|
1724
|
+
},
|
|
1725
|
+
})
|
|
1726
|
+
return {
|
|
1727
|
+
process: proc,
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
export const Gleam: Info = {
|
|
1733
|
+
id: "gleam",
|
|
1734
|
+
extensions: [".gleam"],
|
|
1735
|
+
root: NearestRoot(["gleam.toml"]),
|
|
1736
|
+
async spawn(root) {
|
|
1737
|
+
const gleam = Bun.which("gleam")
|
|
1738
|
+
if (!gleam) {
|
|
1739
|
+
log.info("gleam not found, please install gleam first")
|
|
1740
|
+
return
|
|
1741
|
+
}
|
|
1742
|
+
return {
|
|
1743
|
+
process: spawn(gleam, ["lsp"], {
|
|
1744
|
+
cwd: root,
|
|
1745
|
+
}),
|
|
1746
|
+
}
|
|
1747
|
+
},
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
export const Clojure: Info = {
|
|
1751
|
+
id: "clojure-lsp",
|
|
1752
|
+
extensions: [".clj", ".cljs", ".cljc", ".edn"],
|
|
1753
|
+
root: NearestRoot(["deps.edn", "project.clj", "shadow-cljs.edn", "bb.edn", "build.boot"]),
|
|
1754
|
+
async spawn(root) {
|
|
1755
|
+
let bin = Bun.which("clojure-lsp")
|
|
1756
|
+
if (!bin && process.platform === "win32") {
|
|
1757
|
+
bin = Bun.which("clojure-lsp.exe")
|
|
1758
|
+
}
|
|
1759
|
+
if (!bin) {
|
|
1760
|
+
log.info("clojure-lsp not found, please install clojure-lsp first")
|
|
1761
|
+
return
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
process: spawn(bin, ["listen"], {
|
|
1765
|
+
cwd: root,
|
|
1766
|
+
}),
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
export const Nixd: Info = {
|
|
1772
|
+
id: "nixd",
|
|
1773
|
+
extensions: [".nix"],
|
|
1774
|
+
root: async (file) => {
|
|
1775
|
+
// First, look for flake.nix - the most reliable Nix project root indicator
|
|
1776
|
+
const flakeRoot = await NearestRoot(["flake.nix"])(file)
|
|
1777
|
+
if (flakeRoot && flakeRoot !== Instance.directory) return flakeRoot
|
|
1778
|
+
|
|
1779
|
+
// If no flake.nix, fall back to git repository root
|
|
1780
|
+
if (Instance.worktree && Instance.worktree !== Instance.directory) return Instance.worktree
|
|
1781
|
+
|
|
1782
|
+
// Finally, use the instance directory as fallback
|
|
1783
|
+
return Instance.directory
|
|
1784
|
+
},
|
|
1785
|
+
async spawn(root) {
|
|
1786
|
+
const nixd = Bun.which("nixd")
|
|
1787
|
+
if (!nixd) {
|
|
1788
|
+
log.info("nixd not found, please install nixd first")
|
|
1789
|
+
return
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
process: spawn(nixd, [], {
|
|
1793
|
+
cwd: root,
|
|
1794
|
+
env: {
|
|
1795
|
+
...process.env,
|
|
1796
|
+
},
|
|
1797
|
+
}),
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
export const Tinymist: Info = {
|
|
1803
|
+
id: "tinymist",
|
|
1804
|
+
extensions: [".typ", ".typc"],
|
|
1805
|
+
root: NearestRoot(["typst.toml"]),
|
|
1806
|
+
async spawn(root) {
|
|
1807
|
+
let bin = Bun.which("tinymist", {
|
|
1808
|
+
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
|
1809
|
+
})
|
|
1810
|
+
|
|
1811
|
+
if (!bin) {
|
|
1812
|
+
if (Flag.RIRD_DISABLE_LSP_DOWNLOAD) return
|
|
1813
|
+
log.info("downloading tinymist from GitHub releases")
|
|
1814
|
+
|
|
1815
|
+
const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest")
|
|
1816
|
+
if (!response.ok) {
|
|
1817
|
+
log.error("Failed to fetch tinymist release info")
|
|
1818
|
+
return
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
const release = (await response.json()) as {
|
|
1822
|
+
tag_name?: string
|
|
1823
|
+
assets?: { name?: string; browser_download_url?: string }[]
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const platform = process.platform
|
|
1827
|
+
const arch = process.arch
|
|
1828
|
+
|
|
1829
|
+
const tinymistArch = arch === "arm64" ? "aarch64" : "x86_64"
|
|
1830
|
+
let tinymistPlatform: string
|
|
1831
|
+
let ext: string
|
|
1832
|
+
|
|
1833
|
+
if (platform === "darwin") {
|
|
1834
|
+
tinymistPlatform = "apple-darwin"
|
|
1835
|
+
ext = "tar.gz"
|
|
1836
|
+
} else if (platform === "win32") {
|
|
1837
|
+
tinymistPlatform = "pc-windows-msvc"
|
|
1838
|
+
ext = "zip"
|
|
1839
|
+
} else {
|
|
1840
|
+
tinymistPlatform = "unknown-linux-gnu"
|
|
1841
|
+
ext = "tar.gz"
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
const assetName = `tinymist-${tinymistArch}-${tinymistPlatform}.${ext}`
|
|
1845
|
+
|
|
1846
|
+
const assets = release.assets ?? []
|
|
1847
|
+
const asset = assets.find((a) => a.name === assetName)
|
|
1848
|
+
if (!asset?.browser_download_url) {
|
|
1849
|
+
log.error(`Could not find asset ${assetName} in tinymist release`)
|
|
1850
|
+
return
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const downloadResponse = await fetch(asset.browser_download_url)
|
|
1854
|
+
if (!downloadResponse.ok) {
|
|
1855
|
+
log.error("Failed to download tinymist")
|
|
1856
|
+
return
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const tempPath = path.join(Global.Path.bin, assetName)
|
|
1860
|
+
await Bun.file(tempPath).write(downloadResponse)
|
|
1861
|
+
|
|
1862
|
+
if (ext === "zip") {
|
|
1863
|
+
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
|
|
1864
|
+
.then(() => true)
|
|
1865
|
+
.catch((error) => {
|
|
1866
|
+
log.error("Failed to extract tinymist archive", { error })
|
|
1867
|
+
return false
|
|
1868
|
+
})
|
|
1869
|
+
if (!ok) return
|
|
1870
|
+
} else {
|
|
1871
|
+
await $`tar -xzf ${tempPath} --strip-components=1`.cwd(Global.Path.bin).quiet().nothrow()
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
await fs.rm(tempPath, { force: true })
|
|
1875
|
+
|
|
1876
|
+
bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
|
|
1877
|
+
|
|
1878
|
+
if (!(await Bun.file(bin).exists())) {
|
|
1879
|
+
log.error("Failed to extract tinymist binary")
|
|
1880
|
+
return
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (platform !== "win32") {
|
|
1884
|
+
await $`chmod +x ${bin}`.quiet().nothrow()
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
log.info("installed tinymist", { bin })
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
return {
|
|
1891
|
+
process: spawn(bin, { cwd: root }),
|
|
1892
|
+
}
|
|
1893
|
+
},
|
|
1894
|
+
}
|
|
1895
|
+
}
|