mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-24 11:14:23 +01:00
lsp progress
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
{
|
||||
|
||||
5
packages/opencode/src/cli/cmd/cmd.ts
Normal file
5
packages/opencode/src/cli/cmd/cmd.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { CommandModule } from "yargs"
|
||||
|
||||
export function cmd<T, U>(input: CommandModule<T, U>) {
|
||||
return input
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
21
packages/opencode/src/cli/cmd/scrap.ts
Normal file
21
packages/opencode/src/cli/cmd/scrap.ts
Normal 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)
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
6
packages/opencode/src/cli/version.ts
Normal file
6
packages/opencode/src/cli/version.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
declare global {
|
||||
const OPENCODE_VERSION: string
|
||||
}
|
||||
|
||||
export const VERSION =
|
||||
typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user