fix(config): load lsp config from jsonc configuration files
Signed-off-by: Raphanus Lo <coldturnip@gmail.com>
This commit is contained in:
parent
3b2d3acd17
commit
f80b72c2b7
@ -280,10 +280,10 @@ To remove oh-my-opencode:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Remove user config
|
# Remove user config
|
||||||
rm -f ~/.config/opencode/oh-my-opencode.json
|
rm -f ~/.config/opencode/oh-my-opencode.json ~/.config/opencode/oh-my-opencode.jsonc
|
||||||
|
|
||||||
# Remove project config (if exists)
|
# Remove project config (if exists)
|
||||||
rm -f .opencode/oh-my-opencode.json
|
rm -f .opencode/oh-my-opencode.json .opencode/oh-my-opencode.jsonc
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Verify removal**
|
3. **Verify removal**
|
||||||
@ -314,7 +314,7 @@ Highly opinionated, but adjustable to taste.
|
|||||||
See the full [Configuration Documentation](docs/configurations.md) for detailed information.
|
See the full [Configuration Documentation](docs/configurations.md) for detailed information.
|
||||||
|
|
||||||
**Quick Overview:**
|
**Quick Overview:**
|
||||||
- **Config Locations**: `.opencode/oh-my-opencode.json` (project) or `~/.config/opencode/oh-my-opencode.json` (user)
|
- **Config Locations**: `.opencode/oh-my-opencode.jsonc` or `.opencode/oh-my-opencode.json` (project), `~/.config/opencode/oh-my-opencode.jsonc` or `~/.config/opencode/oh-my-opencode.json` (user)
|
||||||
- **JSONC Support**: Comments and trailing commas supported
|
- **JSONC Support**: Comments and trailing commas supported
|
||||||
- **Agents**: Override models, temperatures, prompts, and permissions for any agent
|
- **Agents**: Override models, temperatures, prompts, and permissions for any agent
|
||||||
- **Built-in Skills**: `playwright` (browser automation), `git-master` (atomic commits)
|
- **Built-in Skills**: `playwright` (browser automation), `git-master` (atomic commits)
|
||||||
|
|||||||
@ -38,13 +38,13 @@ It asks about your providers (Claude, OpenAI, Gemini, etc.) and generates optima
|
|||||||
## Config File Locations
|
## Config File Locations
|
||||||
|
|
||||||
Config file locations (priority order):
|
Config file locations (priority order):
|
||||||
1. `.opencode/oh-my-opencode.json` (project)
|
1. `.opencode/oh-my-opencode.jsonc` or `.opencode/oh-my-opencode.json` (project; prefers `.jsonc` when both exist)
|
||||||
2. User config (platform-specific):
|
2. User config (platform-specific; prefers `.jsonc` when both exist):
|
||||||
|
|
||||||
| Platform | User Config Path |
|
| Platform | User Config Path |
|
||||||
| --------------- | ----------------------------------------------------------------------------------------------------------- |
|
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Windows** | `~/.config/opencode/oh-my-opencode.json` (preferred) or `%APPDATA%\opencode\oh-my-opencode.json` (fallback) |
|
| **Windows** | `~/.config/opencode/oh-my-opencode.jsonc` (preferred) or `~/.config/opencode/oh-my-opencode.json` (fallback); `%APPDATA%\opencode\oh-my-opencode.jsonc` / `%APPDATA%\opencode\oh-my-opencode.json` (fallback) |
|
||||||
| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.json` |
|
| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.jsonc` (preferred) or `~/.config/opencode/oh-my-opencode.json` (fallback) |
|
||||||
|
|
||||||
Schema autocomplete supported:
|
Schema autocomplete supported:
|
||||||
|
|
||||||
@ -1061,9 +1061,10 @@ Don't want them? Disable via `disabled_mcps` in `~/.config/opencode/oh-my-openco
|
|||||||
|
|
||||||
OpenCode provides LSP tools for analysis.
|
OpenCode provides LSP tools for analysis.
|
||||||
Oh My OpenCode adds refactoring tools (rename, code actions).
|
Oh My OpenCode adds refactoring tools (rename, code actions).
|
||||||
All OpenCode LSP configs and custom settings (from opencode.json) are supported, plus additional Oh My OpenCode-specific settings.
|
All OpenCode LSP configs and custom settings (from `opencode.jsonc` / `opencode.json`) are supported, plus additional Oh My OpenCode-specific settings.
|
||||||
|
For config discovery, `.jsonc` takes precedence over `.json` when both exist (applies to both `opencode.*` and `oh-my-opencode.*`).
|
||||||
|
|
||||||
Add LSP servers via the `lsp` option in `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.json`:
|
Add LSP servers via the `lsp` option in `~/.config/opencode/oh-my-opencode.jsonc` / `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.jsonc` / `.opencode/oh-my-opencode.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { describe, it, expect } from "bun:test"
|
import { describe, it, expect } from "bun:test"
|
||||||
import { writeFileSync, unlinkSync } from "fs"
|
import { writeFileSync, unlinkSync, mkdirSync, rmSync } from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import { tmpdir } from "os"
|
import { tmpdir } from "os"
|
||||||
import { loadJsonFile } from "./server-config-loader"
|
import { loadJsonFile, getConfigPaths, getMergedServers } from "./server-config-loader"
|
||||||
|
|
||||||
describe("loadJsonFile", () => {
|
describe("loadJsonFile", () => {
|
||||||
it("parses JSONC config files with comments correctly", () => {
|
it("parses JSONC config files with comments correctly", () => {
|
||||||
@ -36,4 +36,126 @@ describe("loadJsonFile", () => {
|
|||||||
// cleanup
|
// cleanup
|
||||||
unlinkSync(tempPath)
|
unlinkSync(tempPath)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
it("discovers JSONC-only user config (oh-my-opencode.jsonc)", () => {
|
||||||
|
const originalEnv = process.env.OPENCODE_CONFIG_DIR
|
||||||
|
const tempBase = join(tmpdir(), `omo-test-user-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||||
|
try {
|
||||||
|
mkdirSync(tempBase, { recursive: true })
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = tempBase
|
||||||
|
|
||||||
|
const userJsonc = `{
|
||||||
|
// user jsonc config
|
||||||
|
"lsp": {
|
||||||
|
"user-jsonc": {
|
||||||
|
"command": ["user-jsonc-cmd"],
|
||||||
|
"extensions": [".ujs"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const userPath = join(tempBase, "oh-my-opencode.jsonc")
|
||||||
|
writeFileSync(userPath, userJsonc, "utf-8")
|
||||||
|
|
||||||
|
const servers = getMergedServers()
|
||||||
|
const found = servers.find(s => s.id === "user-jsonc" && s.source === "user")
|
||||||
|
expect(found !== undefined).toBe(true)
|
||||||
|
} finally {
|
||||||
|
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
|
else process.env.OPENCODE_CONFIG_DIR = originalEnv
|
||||||
|
rmSync(tempBase, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("discovers JSONC-only opencode config (opencode.jsonc)", () => {
|
||||||
|
const originalEnv = process.env.OPENCODE_CONFIG_DIR
|
||||||
|
const tempBase = join(tmpdir(), `omo-test-oc-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||||
|
try {
|
||||||
|
mkdirSync(tempBase, { recursive: true })
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = tempBase
|
||||||
|
|
||||||
|
const opencodeJsonc = `{
|
||||||
|
// opencode jsonc config
|
||||||
|
"lsp": {
|
||||||
|
"opencode-jsonc": {
|
||||||
|
"command": ["opencode-jsonc-cmd"],
|
||||||
|
"extensions": [".ocjs"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const opencodePath = join(tempBase, "opencode.jsonc")
|
||||||
|
writeFileSync(opencodePath, opencodeJsonc, "utf-8")
|
||||||
|
|
||||||
|
const servers = getMergedServers()
|
||||||
|
const found = servers.find(s => s.id === "opencode-jsonc" && s.source === "opencode")
|
||||||
|
expect(found !== undefined).toBe(true)
|
||||||
|
} finally {
|
||||||
|
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
|
else process.env.OPENCODE_CONFIG_DIR = originalEnv
|
||||||
|
rmSync(tempBase, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("discovers JSONC-only project config (.opencode/oh-my-opencode.jsonc)", () => {
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
const tempProject = join(tmpdir(), `omo-test-project-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||||
|
try {
|
||||||
|
mkdirSync(join(tempProject, ".opencode"), { recursive: true })
|
||||||
|
const projectJsonc = `{
|
||||||
|
// project jsonc config
|
||||||
|
"lsp": {
|
||||||
|
"project-jsonc": {
|
||||||
|
"command": ["project-jsonc-cmd"],
|
||||||
|
"extensions": [".pjs"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const projectPath = join(tempProject, ".opencode", "oh-my-opencode.jsonc")
|
||||||
|
writeFileSync(projectPath, projectJsonc, "utf-8")
|
||||||
|
|
||||||
|
process.chdir(tempProject)
|
||||||
|
const servers = getMergedServers()
|
||||||
|
const found = servers.find(s => s.id === "project-jsonc" && s.source === "project")
|
||||||
|
expect(found !== undefined).toBe(true)
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
rmSync(tempProject, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prefers .jsonc over .json when both exist for same config id", () => {
|
||||||
|
const originalEnv = process.env.OPENCODE_CONFIG_DIR
|
||||||
|
const tempBase = join(tmpdir(), `omo-test-precedence-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
||||||
|
try {
|
||||||
|
mkdirSync(tempBase, { recursive: true })
|
||||||
|
process.env.OPENCODE_CONFIG_DIR = tempBase
|
||||||
|
|
||||||
|
const jsonContent = `{
|
||||||
|
"lsp": {
|
||||||
|
"conflict": {
|
||||||
|
"command": ["from-json"],
|
||||||
|
"extensions": [".j"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const jsoncContent = `{
|
||||||
|
// jsonc should take precedence
|
||||||
|
"lsp": {
|
||||||
|
"conflict": {
|
||||||
|
"command": ["from-jsonc"],
|
||||||
|
"extensions": [".jc"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
writeFileSync(join(tempBase, "oh-my-opencode.json"), jsonContent, "utf-8")
|
||||||
|
writeFileSync(join(tempBase, "oh-my-opencode.jsonc"), jsoncContent, "utf-8")
|
||||||
|
|
||||||
|
const servers = getMergedServers()
|
||||||
|
const found = servers.find(s => s.id === "conflict" && s.source === "user")
|
||||||
|
expect(found?.command && Array.isArray(found.command) && found.command[0] === "from-jsonc").toBe(true)
|
||||||
|
} finally {
|
||||||
|
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
|
else process.env.OPENCODE_CONFIG_DIR = originalEnv
|
||||||
|
rmSync(tempBase, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { join } from "path"
|
|||||||
import { BUILTIN_SERVERS } from "./constants"
|
import { BUILTIN_SERVERS } from "./constants"
|
||||||
import type { ResolvedServer } from "./types"
|
import type { ResolvedServer } from "./types"
|
||||||
import { getOpenCodeConfigDir } from "../../shared"
|
import { getOpenCodeConfigDir } from "../../shared"
|
||||||
import { parseJsonc } from "../../shared/jsonc-parser"
|
import { parseJsonc, detectConfigFile } from "../../shared/jsonc-parser"
|
||||||
|
|
||||||
interface LspEntry {
|
interface LspEntry {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@ -38,9 +38,9 @@ export function getConfigPaths(): { project: string; user: string; opencode: str
|
|||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||||
return {
|
return {
|
||||||
project: join(cwd, ".opencode", "oh-my-opencode.json"),
|
project: detectConfigFile(join(cwd, ".opencode", "oh-my-opencode")).path,
|
||||||
user: join(configDir, "oh-my-opencode.json"),
|
user: detectConfigFile(join(configDir, "oh-my-opencode")).path,
|
||||||
opencode: join(configDir, "opencode.json"),
|
opencode: detectConfigFile(join(configDir, "opencode")).path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user