|
|
@@ -6,10 +6,7 @@ import { Log } from "../util/log"
|
|
|
import { BunProc } from "../bun"
|
|
|
import { $ } from "bun"
|
|
|
import fs from "fs/promises"
|
|
|
-import { unique } from "remeda"
|
|
|
-import { Ripgrep } from "../file/ripgrep"
|
|
|
-import type { LSPClient } from "./client"
|
|
|
-import { withTimeout } from "../util/timeout"
|
|
|
+import { Filesystem } from "../util/filesystem"
|
|
|
|
|
|
export namespace LSPServer {
|
|
|
const log = Log.create({ service: "lsp.server" })
|
|
|
@@ -17,19 +14,21 @@ export namespace LSPServer {
|
|
|
export interface Handle {
|
|
|
process: ChildProcessWithoutNullStreams
|
|
|
initialization?: Record<string, any>
|
|
|
- onInitialized?: (lsp: LSPClient.Info) => Promise<void>
|
|
|
}
|
|
|
|
|
|
- type RootsFunction = (app: App.Info) => Promise<string[]>
|
|
|
+ type RootFunction = (file: string, app: App.Info) => Promise<string | undefined>
|
|
|
|
|
|
- const SimpleRoots = (patterns: string[]): RootsFunction => {
|
|
|
- return async (app) => {
|
|
|
- const files = await Ripgrep.files({
|
|
|
- glob: patterns.map((p) => `**/${p}`),
|
|
|
- cwd: app.path.root,
|
|
|
+ const NearestRoot = (patterns: string[]): RootFunction => {
|
|
|
+ return async (file, app) => {
|
|
|
+ const files = Filesystem.up({
|
|
|
+ targets: patterns,
|
|
|
+ start: path.dirname(file),
|
|
|
+ stop: app.path.root,
|
|
|
})
|
|
|
- const dirs = files.map((file) => path.dirname(file))
|
|
|
- return unique(dirs).map((dir) => path.join(app.path.root, dir))
|
|
|
+ const first = await files.next()
|
|
|
+ await files.return()
|
|
|
+ if (!first.value) return app.path.root
|
|
|
+ return path.dirname(first.value)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -37,13 +36,13 @@ export namespace LSPServer {
|
|
|
id: string
|
|
|
extensions: string[]
|
|
|
global?: boolean
|
|
|
- roots: (app: App.Info) => Promise<string[]>
|
|
|
+ root: RootFunction
|
|
|
spawn(app: App.Info, root: string): Promise<Handle | undefined>
|
|
|
}
|
|
|
|
|
|
export const Typescript: Info = {
|
|
|
id: "typescript",
|
|
|
- roots: async (app) => [app.path.root],
|
|
|
+ root: NearestRoot(["tsconfig.json", "package.json", "jsconfig.json"]),
|
|
|
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
|
|
async spawn(app, root) {
|
|
|
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", app.path.cwd).catch(() => {})
|
|
|
@@ -62,33 +61,16 @@ export namespace LSPServer {
|
|
|
path: tsserver,
|
|
|
},
|
|
|
},
|
|
|
- // tsserver sucks and won't start processing codebase until you open a file
|
|
|
- onInitialized: async (lsp) => {
|
|
|
- const [hint] = await Ripgrep.files({
|
|
|
- cwd: lsp.root,
|
|
|
- glob: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs", "*.cjs", "*.mts", "*.cts"],
|
|
|
- limit: 1,
|
|
|
- })
|
|
|
- const wait = new Promise<void>(async (resolve) => {
|
|
|
- const notif = lsp.connection.onNotification("$/progress", (params) => {
|
|
|
- if (params.value.kind !== "end") return
|
|
|
- notif.dispose()
|
|
|
- resolve()
|
|
|
- })
|
|
|
- await lsp.notify.open({ path: path.join(lsp.root, hint) })
|
|
|
- })
|
|
|
- await withTimeout(wait, 5_000)
|
|
|
- },
|
|
|
}
|
|
|
},
|
|
|
}
|
|
|
|
|
|
export const Gopls: Info = {
|
|
|
id: "golang",
|
|
|
- roots: async (app) => {
|
|
|
- const work = await SimpleRoots(["go.work"])(app)
|
|
|
- if (work.length > 0) return work
|
|
|
- return SimpleRoots(["go.mod", "go.sum"])(app)
|
|
|
+ root: async (file, app) => {
|
|
|
+ const work = await NearestRoot(["go.work"])(file, app)
|
|
|
+ if (work) return work
|
|
|
+ return NearestRoot(["go.mod", "go.sum"])(file, app)
|
|
|
},
|
|
|
extensions: [".go"],
|
|
|
async spawn(_, root) {
|
|
|
@@ -125,7 +107,7 @@ export namespace LSPServer {
|
|
|
|
|
|
export const RubyLsp: Info = {
|
|
|
id: "ruby-lsp",
|
|
|
- roots: SimpleRoots(["Gemfile"]),
|
|
|
+ root: NearestRoot(["Gemfile"]),
|
|
|
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
|
|
async spawn(_, root) {
|
|
|
let bin = Bun.which("ruby-lsp", {
|
|
|
@@ -166,14 +148,7 @@ export namespace LSPServer {
|
|
|
export const Pyright: Info = {
|
|
|
id: "pyright",
|
|
|
extensions: [".py", ".pyi"],
|
|
|
- roots: SimpleRoots([
|
|
|
- "pyproject.toml",
|
|
|
- "setup.py",
|
|
|
- "setup.cfg",
|
|
|
- "requirements.txt",
|
|
|
- "Pipfile",
|
|
|
- "pyrightconfig.json",
|
|
|
- ]),
|
|
|
+ root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
|
|
|
async spawn(_, root) {
|
|
|
const proc = spawn(BunProc.which(), ["x", "pyright-langserver", "--stdio"], {
|
|
|
cwd: root,
|
|
|
@@ -191,7 +166,7 @@ export namespace LSPServer {
|
|
|
export const ElixirLS: Info = {
|
|
|
id: "elixir-ls",
|
|
|
extensions: [".ex", ".exs"],
|
|
|
- roots: SimpleRoots(["mix.exs", "mix.lock"]),
|
|
|
+ root: NearestRoot(["mix.exs", "mix.lock"]),
|
|
|
async spawn(_, root) {
|
|
|
let binary = Bun.which("elixir-ls")
|
|
|
if (!binary) {
|
|
|
@@ -246,7 +221,7 @@ export namespace LSPServer {
|
|
|
export const Zls: Info = {
|
|
|
id: "zls",
|
|
|
extensions: [".zig", ".zon"],
|
|
|
- roots: SimpleRoots(["build.zig"]),
|
|
|
+ root: NearestRoot(["build.zig"]),
|
|
|
async spawn(_, root) {
|
|
|
let bin = Bun.which("zls", {
|
|
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|