diff --git a/docs/API-Reference/language/CodeInspection.md b/docs/API-Reference/language/CodeInspection.md
index e73b770a91..49c0e9d862 100644
--- a/docs/API-Reference/language/CodeInspection.md
+++ b/docs/API-Reference/language/CodeInspection.md
@@ -164,6 +164,7 @@ Each error object in the results should have the following structure:
type:?Type ,
fix: { // an optional fix, if present will show the fix button
replaceText: "text to replace the offset given below",
+ title: "optional tooltip describing what the fix does",
rangeOffset: {
start: number,
end: number
@@ -195,6 +196,7 @@ Each error object in the results should have the following structure:
| type | [Type](#Type) | The type of the error. Defaults to `Type.WARNING` if unspecified. |
| fix | Object | An optional fix object. |
| fix.replaceText | string | The text to replace the error with. |
+| fix.title | string | Optional tooltip on the Fix button describing what the fix does. |
| fix.rangeOffset | Object | The range within the text to replace. |
| fix.rangeOffset.start | number | The start offset of the range. |
| fix.rangeOffset.end | number | The end offset of the range. If no errors are found, return either `null`(treated as file is problem free) or an object with a zero-length `errors` array. Always use `message` to safely display the error as text. If you want to display HTML error message, then explicitly use `htmlMessage` to display it. Both `message` and `htmlMessage` can be used simultaneously. After scanning the file, if you need to omit the lint result, return or resolve with `{isIgnored: true}`. This prevents the file from being marked with a no errors tick mark in the status bar and excludes the linter from the problems panel. |
diff --git a/docs/API-Reference/utils/NodeUtils.md b/docs/API-Reference/utils/NodeUtils.md
index 9243e256a3..e9a44eb11d 100644
--- a/docs/API-Reference/utils/NodeUtils.md
+++ b/docs/API-Reference/utils/NodeUtils.md
@@ -56,6 +56,72 @@ This is only available in the native app.
| url | string |
| browserName | string |
+
+
+## downloadFile(url, destFile, [options]) ⇒ Promise.<void>
+Downloads a URL to a file on disk, fully node-side (native fetch, streamed to disk).
+When an expected sha256 is given, a mismatch deletes the file and rejects - a resolved
+promise means the file holds exactly the pinned bytes.
+This is only available in the native app.
+
+**Kind**: global function
+
+| Param | Type | Description |
+| --- | --- | --- |
+| url | string | download URL (redirects followed) |
+| destFile | string | platform path to write (parent directories created) |
+| [options] | Object | |
+| [options.sha256] | string | expected hex digest of the downloaded bytes |
+| [options.progress] | function | called with (transferredBytes, totalBytes) as the download advances; totalBytes is 0 if the server sent no length |
+
+
+
+## extractZipFile(zipPath, destDir) ⇒ Promise.<void>
+Extracts a zip file into a directory node-side (stdlib only, no browser JSZip; creates the
+directory if missing, restores unix executable bits recorded in the archive). Python wheels
+are plain zips, so this installs those too.
+This is only available in the native app.
+
+**Kind**: global function
+
+| Param | Type | Description |
+| --- | --- | --- |
+| zipPath | string | platform path of the zip file |
+| destDir | string | platform path of the directory to extract into |
+
+
+
+## setExecutableBits(filePath) ⇒ Promise.<void>
+Marks a file as executable (chmod 755); no-op on Windows. For binaries whose archives did
+not carry unix mode bits.
+This is only available in the native app.
+
+**Kind**: global function
+
+| Param | Type | Description |
+| --- | --- | --- |
+| filePath | string | platform path of the file |
+
+
+
+## execFileWithInput(command, [args], [options]) ⇒ Promise.<{code: number, stdout: string, stderr: string}>
+Runs an executable with the given args, feeding it text on stdin and capturing its output -
+a one-shot filter-style invocation (e.g. `ruff format -` for the Python beautifier). No
+shell is involved. Resolves with the exit code rather than rejecting on non-zero, so
+callers can read stderr for the reason.
+This is only available in the native app.
+
+**Kind**: global function
+
+| Param | Type | Description |
+| --- | --- | --- |
+| command | string | platform path of the executable (or a PATH command name) |
+| [args] | Array.<string> | |
+| [options] | Object | |
+| [options.stdinText] | string | written to the process's stdin, then closed |
+| [options.cwd] | string | working directory |
+| [options.timeoutMs] | number | kill the process and reject after this long |
+
## getEnvironmentVariable(varName) ⇒ Promise.<string>
diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js
index 961ea91419..81e69f8430 100644
--- a/gulpfile.js/index.js
+++ b/gulpfile.js/index.js
@@ -909,8 +909,8 @@ function _renameExtensionConcatAsExtensionJSInDist(extensionName) {
}
const minifyableExtensions = ["CloseOthers", "CodeFolding", "DebugCommands", "Git",
- "HealthData", "JavaScriptCodeHints", "JavaScriptRefactoring", "PHPSupport", "QuickView",
- "TypeScriptSupport"];
+ "HealthData", "JavaScriptCodeHints", "JavaScriptRefactoring", "PHPSupport", "PythonSupport",
+ "QuickView", "TypeScriptSupport"];
// extensions that nned not be minified either coz they are single file extensions or some other reason.
const nonMinifyExtensions = ["CSSAtRuleCodeHints", "CSSCodeHints",
"CSSPseudoSelectorHints", "DarkTheme", "DocCommentHints", "HandlebarsSupport", "HTMLCodeHints",
diff --git a/src-node/lsp-client.js b/src-node/lsp-client.js
index 0bbfd0bdce..1d3724632e 100644
--- a/src-node/lsp-client.js
+++ b/src-node/lsp-client.js
@@ -153,10 +153,37 @@ function handleMessage(serverId, msg) {
}
}
+/**
+ * Resolve a workspace/configuration item's dotted section (e.g. "python" or "python.pyrefly")
+ * inside a server's registered workspaceConfiguration object. Returns null when any path
+ * segment is missing - the spec's "no config" answer.
+ * @param {?Object} config - the server's workspaceConfiguration (may be undefined)
+ * @param {?string} section - the requested section; no section means the whole object
+ * @return {*}
+ */
+function _lookupConfigSection(config, section) {
+ if (!config) {
+ return null;
+ }
+ if (!section) {
+ return config;
+ }
+ let value = config;
+ for (const part of section.split('.')) {
+ if (value === null || typeof value !== 'object' || !(part in value)) {
+ return null;
+ }
+ value = value[part];
+ }
+ return value;
+}
+
/**
* Answer a server-initiated request with a benign, spec-shaped reply. We advertise minimal client
* capabilities (no dynamic registration, workspace.configuration=false), so servers should rarely
* send these - this is the safety net that guarantees no server hangs awaiting a reply.
+ * Servers that pull configuration regardless (e.g. pyrefly) get the workspaceConfiguration
+ * object registered at startServer; anything else gets the spec's null "no config".
* @param {string} serverId - The server identifier (for logging)
* @param {Object} server - The server state object
* @param {Object} msg - The incoming JSON-RPC request (method + id)
@@ -168,7 +195,8 @@ function _respondToServerRequest(serverId, server, msg) {
// Result must be an array matching params.items length; null entries mean "no config".
response = {
jsonrpc: '2.0', id: msg.id,
- result: ((msg.params && msg.params.items) || []).map(() => null)
+ result: ((msg.params && msg.params.items) || []).map(
+ item => _lookupConfigSection(server.workspaceConfiguration, item && item.section))
};
break;
case 'client/registerCapability':
@@ -205,10 +233,13 @@ exports.ping = async function ping() {
* @param {string} params.command - Command used to spawn the language server
* @param {string[]} [params.args=['--stdio']] - Arguments for the command
* @param {string} params.rootUri - Root URI of the workspace
+ * @param {Object} [params.workspaceConfiguration] - settings tree served to the server's
+ * workspace/configuration pulls (sections resolved by dotted path); pulls answer null
+ * without it
* @returns {Promise