feat(lsp): add oh-my-opencode.json config support with priority
Config file priority (high to low): 1. ./.opencode/oh-my-opencode.json (project) 2. ~/.config/opencode/oh-my-opencode.json (user) 3. ~/.config/opencode/opencode.json (opencode) 4. builtin servers LSP servers sorted by source then priority field (higher = preferred)
This commit is contained in:
@@ -7,97 +7,131 @@ export interface ResolvedServer {
|
|||||||
id: string
|
id: string
|
||||||
command: string[]
|
command: string[]
|
||||||
extensions: string[]
|
extensions: string[]
|
||||||
|
priority: number
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
initialization?: Record<string, unknown>
|
initialization?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OpencodeJsonLspEntry {
|
interface LspEntry {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
command?: string[]
|
command?: string[]
|
||||||
extensions?: string[]
|
extensions?: string[]
|
||||||
|
priority?: number
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
initialization?: Record<string, unknown>
|
initialization?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OpencodeJson {
|
interface ConfigJson {
|
||||||
lsp?: Record<string, OpencodeJsonLspEntry>
|
lsp?: Record<string, LspEntry>
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedOpencodeConfig: OpencodeJson | null = null
|
type ConfigSource = "project" | "user" | "opencode"
|
||||||
|
|
||||||
function loadOpencodeJson(): OpencodeJson {
|
interface ServerWithSource extends ResolvedServer {
|
||||||
if (cachedOpencodeConfig) return cachedOpencodeConfig
|
source: ConfigSource
|
||||||
|
}
|
||||||
|
|
||||||
const configPath = join(homedir(), ".config", "opencode", "opencode.json")
|
function loadJsonFile<T>(path: string): T | null {
|
||||||
if (existsSync(configPath)) {
|
if (!existsSync(path)) return null
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(configPath, "utf-8")
|
return JSON.parse(readFileSync(path, "utf-8")) as T
|
||||||
cachedOpencodeConfig = JSON.parse(content) as OpencodeJson
|
} catch {
|
||||||
return cachedOpencodeConfig
|
return null
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedOpencodeConfig = {}
|
|
||||||
return cachedOpencodeConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisabledServers(): Set<string> {
|
function getConfigPaths(): { project: string; user: string; opencode: string } {
|
||||||
const config = loadOpencodeJson()
|
const cwd = process.cwd()
|
||||||
const disabled = new Set<string>()
|
return {
|
||||||
|
project: join(cwd, ".opencode", "oh-my-opencode.json"),
|
||||||
|
user: join(homedir(), ".config", "opencode", "oh-my-opencode.json"),
|
||||||
|
opencode: join(homedir(), ".config", "opencode", "opencode.json"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAllConfigs(): Map<ConfigSource, ConfigJson> {
|
||||||
|
const paths = getConfigPaths()
|
||||||
|
const configs = new Map<ConfigSource, ConfigJson>()
|
||||||
|
|
||||||
|
const project = loadJsonFile<ConfigJson>(paths.project)
|
||||||
|
if (project) configs.set("project", project)
|
||||||
|
|
||||||
|
const user = loadJsonFile<ConfigJson>(paths.user)
|
||||||
|
if (user) configs.set("user", user)
|
||||||
|
|
||||||
|
const opencode = loadJsonFile<ConfigJson>(paths.opencode)
|
||||||
|
if (opencode) configs.set("opencode", opencode)
|
||||||
|
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMergedServers(): ServerWithSource[] {
|
||||||
|
const configs = loadAllConfigs()
|
||||||
|
const servers: ServerWithSource[] = []
|
||||||
|
const disabled = new Set<string>()
|
||||||
|
const seen = new Set<string>()
|
||||||
|
|
||||||
|
const sources: ConfigSource[] = ["project", "user", "opencode"]
|
||||||
|
|
||||||
|
for (const source of sources) {
|
||||||
|
const config = configs.get(source)
|
||||||
|
if (!config?.lsp) continue
|
||||||
|
|
||||||
if (config.lsp) {
|
|
||||||
for (const [id, entry] of Object.entries(config.lsp)) {
|
for (const [id, entry] of Object.entries(config.lsp)) {
|
||||||
if (entry.disabled) {
|
if (entry.disabled) {
|
||||||
disabled.add(id)
|
disabled.add(id)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return disabled
|
if (seen.has(id)) continue
|
||||||
}
|
|
||||||
|
|
||||||
function getUserLspServers(): Map<string, ResolvedServer> {
|
|
||||||
const config = loadOpencodeJson()
|
|
||||||
const servers = new Map<string, ResolvedServer>()
|
|
||||||
|
|
||||||
if (config.lsp) {
|
|
||||||
for (const [id, entry] of Object.entries(config.lsp)) {
|
|
||||||
if (entry.disabled) continue
|
|
||||||
if (!entry.command || !entry.extensions) continue
|
if (!entry.command || !entry.extensions) continue
|
||||||
|
|
||||||
servers.set(id, {
|
servers.push({
|
||||||
id,
|
id,
|
||||||
command: entry.command,
|
command: entry.command,
|
||||||
extensions: entry.extensions,
|
extensions: entry.extensions,
|
||||||
|
priority: entry.priority ?? 0,
|
||||||
env: entry.env,
|
env: entry.env,
|
||||||
initialization: entry.initialization,
|
initialization: entry.initialization,
|
||||||
|
source,
|
||||||
})
|
})
|
||||||
}
|
seen.add(id)
|
||||||
}
|
|
||||||
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findServerForExtension(ext: string): ResolvedServer | null {
|
|
||||||
const userServers = getUserLspServers()
|
|
||||||
const disabledServers = getDisabledServers()
|
|
||||||
|
|
||||||
for (const server of userServers.values()) {
|
|
||||||
if (server.extensions.includes(ext) && isServerInstalled(server.command)) {
|
|
||||||
return server
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [id, config] of Object.entries(BUILTIN_SERVERS)) {
|
for (const [id, config] of Object.entries(BUILTIN_SERVERS)) {
|
||||||
if (disabledServers.has(id)) continue
|
if (disabled.has(id) || seen.has(id)) continue
|
||||||
if (userServers.has(id)) continue
|
|
||||||
|
|
||||||
if (config.extensions.includes(ext) && isServerInstalled(config.command)) {
|
servers.push({
|
||||||
|
id,
|
||||||
|
command: config.command,
|
||||||
|
extensions: config.extensions,
|
||||||
|
priority: -100,
|
||||||
|
source: "opencode",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers.sort((a, b) => {
|
||||||
|
if (a.source !== b.source) {
|
||||||
|
const order: Record<ConfigSource, number> = { project: 0, user: 1, opencode: 2 }
|
||||||
|
return order[a.source] - order[b.source]
|
||||||
|
}
|
||||||
|
return b.priority - a.priority
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findServerForExtension(ext: string): ResolvedServer | null {
|
||||||
|
const servers = getMergedServers()
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
if (server.extensions.includes(ext) && isServerInstalled(server.command)) {
|
||||||
return {
|
return {
|
||||||
id,
|
id: server.id,
|
||||||
command: config.command,
|
command: server.command,
|
||||||
extensions: config.extensions,
|
extensions: server.extensions,
|
||||||
|
priority: server.priority,
|
||||||
|
env: server.env,
|
||||||
|
initialization: server.initialization,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,23 +159,50 @@ export function isServerInstalled(command: string[]): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllServers(): Array<{ id: string; installed: boolean; extensions: string[]; disabled: boolean }> {
|
export function getAllServers(): Array<{
|
||||||
const result: Array<{ id: string; installed: boolean; extensions: string[]; disabled: boolean }> = []
|
id: string
|
||||||
const userServers = getUserLspServers()
|
installed: boolean
|
||||||
const disabledServers = getDisabledServers()
|
extensions: string[]
|
||||||
|
disabled: boolean
|
||||||
|
source: string
|
||||||
|
priority: number
|
||||||
|
}> {
|
||||||
|
const configs = loadAllConfigs()
|
||||||
|
const servers = getMergedServers()
|
||||||
|
const disabled = new Set<string>()
|
||||||
|
|
||||||
|
for (const config of configs.values()) {
|
||||||
|
if (!config.lsp) continue
|
||||||
|
for (const [id, entry] of Object.entries(config.lsp)) {
|
||||||
|
if (entry.disabled) disabled.add(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Array<{
|
||||||
|
id: string
|
||||||
|
installed: boolean
|
||||||
|
extensions: string[]
|
||||||
|
disabled: boolean
|
||||||
|
source: string
|
||||||
|
priority: number
|
||||||
|
}> = []
|
||||||
|
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
|
|
||||||
for (const server of userServers.values()) {
|
for (const server of servers) {
|
||||||
|
if (seen.has(server.id)) continue
|
||||||
result.push({
|
result.push({
|
||||||
id: server.id,
|
id: server.id,
|
||||||
installed: isServerInstalled(server.command),
|
installed: isServerInstalled(server.command),
|
||||||
extensions: server.extensions,
|
extensions: server.extensions,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
source: server.source,
|
||||||
|
priority: server.priority,
|
||||||
})
|
})
|
||||||
seen.add(server.id)
|
seen.add(server.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of disabledServers) {
|
for (const id of disabled) {
|
||||||
if (seen.has(id)) continue
|
if (seen.has(id)) continue
|
||||||
const builtin = BUILTIN_SERVERS[id]
|
const builtin = BUILTIN_SERVERS[id]
|
||||||
result.push({
|
result.push({
|
||||||
@@ -149,20 +210,14 @@ export function getAllServers(): Array<{ id: string; installed: boolean; extensi
|
|||||||
installed: builtin ? isServerInstalled(builtin.command) : false,
|
installed: builtin ? isServerInstalled(builtin.command) : false,
|
||||||
extensions: builtin?.extensions || [],
|
extensions: builtin?.extensions || [],
|
||||||
disabled: true,
|
disabled: true,
|
||||||
})
|
source: "disabled",
|
||||||
seen.add(id)
|
priority: 0,
|
||||||
}
|
|
||||||
|
|
||||||
for (const [id, config] of Object.entries(BUILTIN_SERVERS)) {
|
|
||||||
if (seen.has(id)) continue
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
id,
|
|
||||||
installed: isServerInstalled(config.command),
|
|
||||||
extensions: config.extensions,
|
|
||||||
disabled: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getConfigPaths_(): { project: string; user: string; opencode: string } {
|
||||||
|
return getConfigPaths()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user