lsp progress

This commit is contained in:
Dax Raad
2025-06-05 23:42:04 -04:00
parent 16520261f4
commit 265f427d2a
13 changed files with 169 additions and 105 deletions

View File

@@ -32,7 +32,11 @@ export namespace App {
const APP_JSON = "app.json"
async function create(input: { cwd: string; version: string }) {
async function create(input: {
cwd: string
version: string
printLogs?: boolean
}) {
const git = await Filesystem.findUp(".git", input.cwd).then((x) =>
x ? path.dirname(x) : undefined,
)
@@ -58,7 +62,7 @@ export namespace App {
}
>()
await Log.file(path.join(data, "log"))
if (!input.printLogs) await Log.file(path.join(data, "log"))
const info: Info = {
user: os.userInfo().username,
@@ -106,7 +110,7 @@ export namespace App {
}
export async function provide<T extends (app: Info) => any>(
input: { cwd: string; version: string },
input: { cwd: string; version: string; printLogs?: boolean },
cb: T,
) {
const app = await create(input)

View File

@@ -53,7 +53,6 @@ export namespace AuthAnthropic {
if (!(await file.exists())) return
const result = await file.json()
const refresh = result.refresh_token
const now = Date.now()
const response = await fetch(
"https://console.anthropic.com/v1/oauth/token",
{

View File

@@ -0,0 +1,5 @@
import type { CommandModule } from "yargs"
export function cmd<T, U>(input: CommandModule<T, U>) {
return input
}

View File

@@ -1,12 +1,12 @@
import type { Argv } from "yargs"
import { App } from "../../app/app"
import { version } from "bun"
import { Bus } from "../../bus"
import { Provider } from "../../provider/provider"
import { Session } from "../../session"
import { Share } from "../../share/share"
import { Message } from "../../session/message"
import { UI } from "../ui"
import { VERSION } from "../version"
export const RunCommand = {
command: "run [message..]",
@@ -26,15 +26,13 @@ export const RunCommand = {
},
handler: async (args: { message: string[]; session?: string }) => {
const message = args.message.join(" ")
await App.provide({ cwd: process.cwd(), version }, async () => {
await App.provide({ cwd: process.cwd(), version: "0.0.0" }, async () => {
await Share.init()
const session = args.session
? await Session.get(args.session)
: await Session.create()
UI.print(UI.Style.TEXT_HIGHLIGHT_BOLD + "◍ OpenCode", version)
UI.print(UI.Style.TEXT_HIGHLIGHT_BOLD + "◍ OpenCode", VERSION)
UI.empty()
UI.print(UI.Style.TEXT_NORMAL_BOLD + "> ", message)
UI.empty()

View File

@@ -0,0 +1,21 @@
import { App } from "../../app/app"
import { VERSION } from "../version"
import { LSP } from "../../lsp"
import { cmd } from "./cmd"
export const ScrapCommand = cmd({
command: "scrap <file>",
builder: (yargs) =>
yargs.positional("file", { type: "string", demandOption: true }),
describe: "test command",
async handler(args) {
await App.provide(
{ cwd: process.cwd(), version: VERSION, printLogs: true },
async () => {
await LSP.file(args.file)
const diagnostics = await LSP.diagnostics()
console.log(diagnostics)
},
)
},
})

View File

@@ -0,0 +1,6 @@
declare global {
const OPENCODE_VERSION: string
}
export const VERSION =
typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"

View File

@@ -1,7 +1,6 @@
import { Log } from "../util/log"
import { z } from "zod"
import { App } from "../app/app"
import { Provider } from "../provider/provider"
import { Filesystem } from "../util/filesystem"
export namespace Config {

View File

@@ -13,21 +13,17 @@ import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
import { LoginAnthropicCommand } from "./cli/cmd/login-anthropic"
import { GenerateCommand } from "./cli/cmd/generate"
declare global {
const OPENCODE_VERSION: string
}
const version = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
import { VERSION } from "./cli/version"
import { ScrapCommand } from "./cli/cmd/scrap"
yargs(hideBin(process.argv))
.scriptName("opencode")
.version(version)
.version(VERSION)
.command({
command: "$0",
describe: "Start OpenCode TUI",
handler: async () => {
await App.provide({ cwd: process.cwd(), version }, async () => {
await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
await Share.init()
const server = Server.listen()
@@ -66,6 +62,7 @@ yargs(hideBin(process.argv))
})
.command(RunCommand)
.command(GenerateCommand)
.command(ScrapCommand)
.command({
command: "login",
describe: "generate credentials for various providers",

View File

@@ -1,4 +1,4 @@
import { spawn } from "child_process"
import { Readable, Writable } from "stream"
import path from "path"
import {
createMessageConnection,
@@ -29,19 +29,60 @@ export namespace LSPClient {
),
}
export async function create(input: { cmd: string[]; serverID: string }) {
log.info("starting client", input)
export async function create(input: {
cmd: string[]
serverID: string
initialization?: any
}) {
const app = App.info()
const [command, ...args] = input.cmd
const server = spawn(command, args, {
stdio: ["pipe", "pipe", "pipe"],
log.info("starting client", {
...input,
cwd: app.path.cwd,
})
const server = Bun.spawn({
cmd: input.cmd,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
cwd: app.path.cwd,
})
const stdout = new Readable({
read() {},
construct(callback) {
const reader = server.stdout.getReader()
const pump = async () => {
try {
while (true) {
const { done, value } = await reader.read()
if (done) {
this.push(null)
break
}
this.push(Buffer.from(value))
}
} catch (error) {
this.destroy(
error instanceof Error ? error : new Error(String(error)),
)
}
}
pump()
callback()
},
})
const stdin = new Writable({
write(chunk, _encoding, callback) {
server.stdin.write(chunk)
callback()
},
})
const connection = createMessageConnection(
new StreamMessageReader(server.stdout),
new StreamMessageWriter(server.stdin),
new StreamMessageReader(stdout),
new StreamMessageWriter(stdin),
)
const diagnostics = new Map<string, Diagnostic[]>()
@@ -50,71 +91,37 @@ export namespace LSPClient {
log.info("textDocument/publishDiagnostics", {
path,
})
const exists = diagnostics.has(path)
diagnostics.set(path, params.diagnostics)
// servers seem to send one blank publishDiagnostics event before the first real one
if (!exists && !params.diagnostics.length) return
Bus.publish(Event.Diagnostics, { path, serverID: input.serverID })
})
connection.onRequest("workspace/configuration", async () => {
return [{}]
})
connection.listen()
await connection.sendRequest("initialize", {
const response = await connection.sendRequest("initialize", {
processId: server.pid,
initializationOptions: {
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
],
tsserver: {
path: require.resolve("typescript/lib/tsserver.js"),
workspaceFolders: [
{
name: "workspace",
uri: "file://" + app.path.cwd,
},
],
initializationOptions: {
...input.initialization,
},
capabilities: {
workspace: {
configuration: true,
didChangeConfiguration: {
dynamicRegistration: true,
},
didChangeWatchedFiles: {
dynamicRegistration: true,
relativePatternSupport: true,
},
},
textDocument: {
synchronization: {
dynamicRegistration: true,
didSave: true,
},
completion: {
completionItem: {},
},
codeLens: {
dynamicRegistration: true,
},
documentSymbol: {},
codeAction: {
codeActionLiteralSupport: {
codeActionKind: {
valueSet: [],
},
},
didOpen: true,
},
publishDiagnostics: {
versionSupport: true,
},
semanticTokens: {
requests: {
range: {},
full: {},
},
tokenTypes: [],
tokenModifiers: [],
formats: [],
},
},
window: {},
},
})
await connection.sendNotification("initialized", {})
@@ -131,6 +138,9 @@ export namespace LSPClient {
},
notify: {
async open(input: { path: string }) {
input.path = path.isAbsolute(input.path)
? input.path
: path.resolve(app.path.cwd, input.path)
const file = Bun.file(input.path)
const text = await file.text()
const opened = files.has(input.path)
@@ -170,6 +180,9 @@ export namespace LSPClient {
return diagnostics
},
async waitForDiagnostics(input: { path: string }) {
input.path = path.isAbsolute(input.path)
? input.path
: path.resolve(app.path.cwd, input.path)
log.info("waiting for diagnostics", input)
let unsub: () => void
let timeout: NodeJS.Timeout

View File

@@ -36,6 +36,7 @@ export namespace LSP {
const client = await LSPClient.create({
cmd: match.command,
serverID: match.id,
initialization: match.initialization,
})
s.clients.set(match.id, client)
}
@@ -87,6 +88,7 @@ export namespace LSP {
const AUTO: {
id: string
command: string[]
initialization?: any
extensions: string[]
install?: () => Promise<void>
}[] = [
@@ -105,10 +107,15 @@ export namespace LSP {
".mtsx",
".ctsx",
],
initialization: {
tsserver: {
path: require.resolve("typescript/lib/tsserver.js"),
},
},
},
{
id: "golang",
command: ["gopls"],
command: ["gopls" /*"-logfile", "gopls.log", "-rpc.trace", "-vv"*/],
extensions: [".go"],
},
]