Skip to content

Commit f80b72c

Browse files
committed
fix(config): load lsp config from jsonc configuration files
Signed-off-by: Raphanus Lo <coldturnip@gmail.com>
1 parent 3b2d3ac commit f80b72c

4 files changed

Lines changed: 141 additions & 18 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,10 @@ To remove oh-my-opencode:
280280

281281
```bash
282282
# Remove user config
283-
rm -f ~/.config/opencode/oh-my-opencode.json
283+
rm -f ~/.config/opencode/oh-my-opencode.json ~/.config/opencode/oh-my-opencode.jsonc
284284

285285
# Remove project config (if exists)
286-
rm -f .opencode/oh-my-opencode.json
286+
rm -f .opencode/oh-my-opencode.json .opencode/oh-my-opencode.jsonc
287287
```
288288

289289
3. **Verify removal**
@@ -314,7 +314,7 @@ Highly opinionated, but adjustable to taste.
314314
See the full [Configuration Documentation](docs/configurations.md) for detailed information.
315315

316316
**Quick Overview:**
317-
- **Config Locations**: `.opencode/oh-my-opencode.json` (project) or `~/.config/opencode/oh-my-opencode.json` (user)
317+
- **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)
318318
- **JSONC Support**: Comments and trailing commas supported
319319
- **Agents**: Override models, temperatures, prompts, and permissions for any agent
320320
- **Built-in Skills**: `playwright` (browser automation), `git-master` (atomic commits)

docs/configurations.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ It asks about your providers (Claude, OpenAI, Gemini, etc.) and generates optima
3838
## Config File Locations
3939

4040
Config file locations (priority order):
41-
1. `.opencode/oh-my-opencode.json` (project)
42-
2. User config (platform-specific):
41+
1. `.opencode/oh-my-opencode.jsonc` or `.opencode/oh-my-opencode.json` (project; prefers `.jsonc` when both exist)
42+
2. User config (platform-specific; prefers `.jsonc` when both exist):
4343

44-
| Platform | User Config Path |
45-
| --------------- | ----------------------------------------------------------------------------------------------------------- |
46-
| **Windows** | `~/.config/opencode/oh-my-opencode.json` (preferred) or `%APPDATA%\opencode\oh-my-opencode.json` (fallback) |
47-
| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.json` |
44+
| Platform | User Config Path |
45+
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
46+
| **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) |
47+
| **macOS/Linux** | `~/.config/opencode/oh-my-opencode.jsonc` (preferred) or `~/.config/opencode/oh-my-opencode.json` (fallback) |
4848

4949
Schema autocomplete supported:
5050

@@ -1061,9 +1061,10 @@ Don't want them? Disable via `disabled_mcps` in `~/.config/opencode/oh-my-openco
10611061

10621062
OpenCode provides LSP tools for analysis.
10631063
Oh My OpenCode adds refactoring tools (rename, code actions).
1064-
All OpenCode LSP configs and custom settings (from opencode.json) are supported, plus additional Oh My OpenCode-specific settings.
1064+
All OpenCode LSP configs and custom settings (from `opencode.jsonc` / `opencode.json`) are supported, plus additional Oh My OpenCode-specific settings.
1065+
For config discovery, `.jsonc` takes precedence over `.json` when both exist (applies to both `opencode.*` and `oh-my-opencode.*`).
10651066

1066-
Add LSP servers via the `lsp` option in `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.json`:
1067+
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`:
10671068

10681069
```json
10691070
{

src/tools/lsp/server-config-loader.test.ts

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { describe, it, expect } from "bun:test"
2-
import { writeFileSync, unlinkSync } from "fs"
2+
import { writeFileSync, unlinkSync, mkdirSync, rmSync } from "fs"
33
import { join } from "path"
44
import { tmpdir } from "os"
5-
import { loadJsonFile } from "./server-config-loader"
5+
import { loadJsonFile, getConfigPaths, getMergedServers } from "./server-config-loader"
66

77
describe("loadJsonFile", () => {
88
it("parses JSONC config files with comments correctly", () => {
@@ -36,4 +36,126 @@ describe("loadJsonFile", () => {
3636
// cleanup
3737
unlinkSync(tempPath)
3838
})
39-
})
39+
40+
it("discovers JSONC-only user config (oh-my-opencode.jsonc)", () => {
41+
const originalEnv = process.env.OPENCODE_CONFIG_DIR
42+
const tempBase = join(tmpdir(), `omo-test-user-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
43+
try {
44+
mkdirSync(tempBase, { recursive: true })
45+
process.env.OPENCODE_CONFIG_DIR = tempBase
46+
47+
const userJsonc = `{
48+
// user jsonc config
49+
"lsp": {
50+
"user-jsonc": {
51+
"command": ["user-jsonc-cmd"],
52+
"extensions": [".ujs"]
53+
}
54+
}
55+
}`
56+
const userPath = join(tempBase, "oh-my-opencode.jsonc")
57+
writeFileSync(userPath, userJsonc, "utf-8")
58+
59+
const servers = getMergedServers()
60+
const found = servers.find(s => s.id === "user-jsonc" && s.source === "user")
61+
expect(found !== undefined).toBe(true)
62+
} finally {
63+
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
64+
else process.env.OPENCODE_CONFIG_DIR = originalEnv
65+
rmSync(tempBase, { recursive: true, force: true })
66+
}
67+
})
68+
69+
it("discovers JSONC-only opencode config (opencode.jsonc)", () => {
70+
const originalEnv = process.env.OPENCODE_CONFIG_DIR
71+
const tempBase = join(tmpdir(), `omo-test-oc-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
72+
try {
73+
mkdirSync(tempBase, { recursive: true })
74+
process.env.OPENCODE_CONFIG_DIR = tempBase
75+
76+
const opencodeJsonc = `{
77+
// opencode jsonc config
78+
"lsp": {
79+
"opencode-jsonc": {
80+
"command": ["opencode-jsonc-cmd"],
81+
"extensions": [".ocjs"]
82+
}
83+
}
84+
}`
85+
const opencodePath = join(tempBase, "opencode.jsonc")
86+
writeFileSync(opencodePath, opencodeJsonc, "utf-8")
87+
88+
const servers = getMergedServers()
89+
const found = servers.find(s => s.id === "opencode-jsonc" && s.source === "opencode")
90+
expect(found !== undefined).toBe(true)
91+
} finally {
92+
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
93+
else process.env.OPENCODE_CONFIG_DIR = originalEnv
94+
rmSync(tempBase, { recursive: true, force: true })
95+
}
96+
})
97+
98+
it("discovers JSONC-only project config (.opencode/oh-my-opencode.jsonc)", () => {
99+
const originalCwd = process.cwd()
100+
const tempProject = join(tmpdir(), `omo-test-project-jsonc-${Date.now()}-${Math.random().toString(36).slice(2)}`)
101+
try {
102+
mkdirSync(join(tempProject, ".opencode"), { recursive: true })
103+
const projectJsonc = `{
104+
// project jsonc config
105+
"lsp": {
106+
"project-jsonc": {
107+
"command": ["project-jsonc-cmd"],
108+
"extensions": [".pjs"]
109+
}
110+
}
111+
}`
112+
const projectPath = join(tempProject, ".opencode", "oh-my-opencode.jsonc")
113+
writeFileSync(projectPath, projectJsonc, "utf-8")
114+
115+
process.chdir(tempProject)
116+
const servers = getMergedServers()
117+
const found = servers.find(s => s.id === "project-jsonc" && s.source === "project")
118+
expect(found !== undefined).toBe(true)
119+
} finally {
120+
process.chdir(originalCwd)
121+
rmSync(tempProject, { recursive: true, force: true })
122+
}
123+
})
124+
125+
it("prefers .jsonc over .json when both exist for same config id", () => {
126+
const originalEnv = process.env.OPENCODE_CONFIG_DIR
127+
const tempBase = join(tmpdir(), `omo-test-precedence-${Date.now()}-${Math.random().toString(36).slice(2)}`)
128+
try {
129+
mkdirSync(tempBase, { recursive: true })
130+
process.env.OPENCODE_CONFIG_DIR = tempBase
131+
132+
const jsonContent = `{
133+
"lsp": {
134+
"conflict": {
135+
"command": ["from-json"],
136+
"extensions": [".j"]
137+
}
138+
}
139+
}`
140+
const jsoncContent = `{
141+
// jsonc should take precedence
142+
"lsp": {
143+
"conflict": {
144+
"command": ["from-jsonc"],
145+
"extensions": [".jc"]
146+
}
147+
}
148+
}`
149+
writeFileSync(join(tempBase, "oh-my-opencode.json"), jsonContent, "utf-8")
150+
writeFileSync(join(tempBase, "oh-my-opencode.jsonc"), jsoncContent, "utf-8")
151+
152+
const servers = getMergedServers()
153+
const found = servers.find(s => s.id === "conflict" && s.source === "user")
154+
expect(found?.command && Array.isArray(found.command) && found.command[0] === "from-jsonc").toBe(true)
155+
} finally {
156+
if (originalEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR
157+
else process.env.OPENCODE_CONFIG_DIR = originalEnv
158+
rmSync(tempBase, { recursive: true, force: true })
159+
}
160+
})
161+
})

src/tools/lsp/server-config-loader.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { join } from "path"
44
import { BUILTIN_SERVERS } from "./constants"
55
import type { ResolvedServer } from "./types"
66
import { getOpenCodeConfigDir } from "../../shared"
7-
import { parseJsonc } from "../../shared/jsonc-parser"
7+
import { parseJsonc, detectConfigFile } from "../../shared/jsonc-parser"
88

99
interface LspEntry {
1010
disabled?: boolean
@@ -38,9 +38,9 @@ export function getConfigPaths(): { project: string; user: string; opencode: str
3838
const cwd = process.cwd()
3939
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
4040
return {
41-
project: join(cwd, ".opencode", "oh-my-opencode.json"),
42-
user: join(configDir, "oh-my-opencode.json"),
43-
opencode: join(configDir, "opencode.json"),
41+
project: detectConfigFile(join(cwd, ".opencode", "oh-my-opencode")).path,
42+
user: detectConfigFile(join(configDir, "oh-my-opencode")).path,
43+
opencode: detectConfigFile(join(configDir, "opencode")).path,
4444
}
4545
}
4646

0 commit comments

Comments
 (0)