Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions packages/opencode/src/lsp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1580,12 +1580,169 @@ export namespace LSPServer {
BUN_BE_BUN: "1",
},
})
// Detect WordPress projects and configure Intelephense accordingly.
// Detection uses cheap filesystem markers only — no PHP content scanning.
const wpDetection: { detected: boolean; hasCore: boolean; corePath?: string } = await (async () => {
// Check 1: WordPress core markers (full installations)
const coreMarkers = ["wp-config.php", "wp-settings.php", "wp-load.php"]
for (const marker of coreMarkers) {
if (await pathExists(path.join(root, marker))) {
log.info("WordPress core detected", { marker, root })
return { detected: true, hasCore: true }
}
}
// Also check parent directories — plugins/themes sit inside wp-content/
for (const depth of ["../..", "../../.."]) {
const candidate = path.resolve(root, depth)
if (await pathExists(path.join(candidate, "wp-load.php"))) {
log.info("WordPress core detected via parent", { candidate, root })
return { detected: true, hasCore: true, corePath: candidate }
}
}

// Check 2: Plugin header — main PHP file with "Plugin Name:" in first 8KB
const mainPluginFile = path.join(root, path.basename(root) + ".php")
const pluginContent = await fs.readFile(mainPluginFile, "utf-8").then((c) => c.slice(0, 8192)).catch(() => null)
if (pluginContent && /\*\s*Plugin Name:/i.test(pluginContent)) {
log.info("WordPress plugin detected", { file: path.basename(mainPluginFile), root })
return { detected: true, hasCore: false }
}

// Check 3: Theme header — style.css with "Theme Name:" in first 8KB
const styleContent = await fs.readFile(path.join(root, "style.css"), "utf-8").then((c) => c.slice(0, 8192)).catch(() => null)
if (styleContent && /Theme Name:/i.test(styleContent)) {
log.info("WordPress theme detected", { root })
return { detected: true, hasCore: false }
}

// Check 4: composer.json requiring WordPress packages
const composerContent = await fs.readFile(path.join(root, "composer.json"), "utf-8").catch(() => null)
if (composerContent) {
try {
const composer = JSON.parse(composerContent)
const allDeps = { ...composer.require, ...composer["require-dev"] }
const wpPackages = Object.keys(allDeps).some(
(pkg) => pkg.startsWith("wordpress/") || pkg.startsWith("wpackagist-") || pkg === "johnpbloch/wordpress" || pkg === "roots/wordpress",
)
if (wpPackages) {
log.info("WordPress composer dependency detected", { root })
return { detected: true, hasCore: false }
}
} catch {
// Invalid JSON — ignore
}
}

return { detected: false, hasCore: false }
})()

// Build Intelephense initialization options for WordPress projects.
// Intelephense includes WordPress stubs by default since v1.12, but setting
// `stubs` replaces the entire default list. We must include all defaults
// alongside "wordpress" to avoid losing standard PHP stubs.
const wpInitialization = wpDetection.detected
? {
intelephense: {
stubs: [
// Intelephense defaults (v1.12+)
"apache",
"bcmath",
"bz2",
"calendar",
"com_dotnet",
"Core",
"ctype",
"curl",
"date",
"dba",
"dom",
"enchant",
"exif",
"FFI",
"fileinfo",
"filter",
"fpm",
"ftp",
"gd",
"gettext",
"gmp",
"hash",
"iconv",
"imap",
"intl",
"json",
"ldap",
"libxml",
"mbstring",
"meta",
"mysqli",
"oci8",
"odbc",
"openssl",
"pcntl",
"pcre",
"PDO",
"pdo_ibm",
"pdo_mysql",
"pdo_pgsql",
"pdo_sqlite",
"pgsql",
"Phar",
"posix",
"pspell",
"random",
"readline",
"Reflection",
"regex",
"session",
"shmop",
"SimpleXML",
"snmp",
"soap",
"sockets",
"sodium",
"SPL",
"sqlite3",
"standard",
"superglobals",
"sysvmsg",
"sysvsem",
"sysvshm",
"tidy",
"tokenizer",
"xml",
"xmlreader",
"xmlrpc",
"xmlwriter",
"xsl",
"Zend OPcache",
"zip",
"zlib",
// WordPress
"wordpress",
],
// Suppress false positives for types from other plugins/packages
// that aren't covered by stubs (especially on Intelephense free tier)
diagnostics: {
undefinedTypes: false,
},
// Point Intelephense at WordPress core files when available
...(wpDetection.hasCore && {
environment: {
includePaths: [wpDetection.corePath ?? root],
},
}),
},
}
: {}

return {
process: proc,
initialization: {
telemetry: {
enabled: false,
},
...wpInitialization,
},
}
},
Expand Down
Loading