Skip to content

Restore Pylance document synchronization on Dev18 - #8601

Open
StellaHuang95 wants to merge 2 commits into
microsoft:mainfrom
StellaHuang95:fix/8592-import-completions
Open

Restore Pylance document synchronization on Dev18#8601
StellaHuang95 wants to merge 2 commits into
microsoft:mainfrom
StellaHuang95:fix/8592-import-completions

Conversation

@StellaHuang95

@StellaHuang95 StellaHuang95 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Root cause

Pylance returns the valid LSP shorthand "textDocumentSync": 2, meaning incremental synchronization.

The regression was introduced by the VSLanguageServerClient generated-capability migration in commit d97ea9c64 (PR 760545). The generated union retains the numeric value as TextDocumentSyncKind.Incremental. The didChange path accepts either the enum or TextDocumentSyncOptions, but the didOpen, didClose, and didSave paths only inspect TextDocumentSyncOptions.

Consequently, VSL does not register didOpen for Pylance. Because the document is never marked open, subsequent didChange notifications are discarded as well. Pylance then has no current editor contents:

  • completion requests are answered from stale/incomplete document state, so import sys omits the sys module;
  • diagnostics configured for open files never run, so unresolved imports produce neither squiggles nor Error List entries.

VSL has an equivalent normalization in commit ac604b1b8, but at the time of investigation that commit was only on feature/dynamic-registration-redesign, not main or release/18.10.

PTVS workaround

VSL performs the initialize exchange directly, before PTVS language-client middle layers can intercept the response. This change therefore wraps only the Pylance stdout stream on Dev18 and, for the initialize response only, expands:

"textDocumentSync": 2

into the semantically equivalent form:

"textDocumentSync": {
  "openClose": true,
  "change": 2,
  "save": { "includeText": false }
}

The stream handles normal LSP framing, preserves any notifications preceding the initialize response, recalculates the UTF-8 Content-Length, and switches to direct pass-through immediately after initialization. It does not inspect or alter completions, diagnostics, document updates, or any later server traffic.

Validation

Validated in the Dev18 Experimental Instance with Pylance 2026.2.109:

  • import sys offers sys in completion results;
  • import xxxx produces an editor squiggle and an Error List diagnostic;
  • automatic completion parentheses still work;
  • LSP tracing contains didOpen, didChange, and publishDiagnostics traffic.

No tests were added or modified.

Fixes #8592
Fixes #8596

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5679231-48fb-4e6f-ba92-12102f084440
@StellaHuang95
StellaHuang95 requested a review from a team as a code owner August 4, 2026 00:15
Copilot AI review requested due to automatic review settings August 4, 2026 00:15
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores correct document synchronization with Pylance on Dev18 by normalizing Pylance’s numeric textDocumentSync initialize capability into the equivalent options object, working around a Dev18 VSLanguageServerClient capability-union regression without changing post-initialize LSP traffic.

Changes:

  • Add a Dev18-only stdout stream wrapper that parses LSP frames and rewrites the initialize response capability when textDocumentSync is sent as a number.
  • Wire the wrapper into the Pylance LanguageServer connection creation (Dev18 only).
  • Include the new stream implementation in the PythonTools project build.
Show a summary per file
File Description
Python/Product/PythonTools/PythonTools/LanguageServerClient/StreamHacking/PylanceInitializeResponseStream.cs New stream wrapper that buffers/parses LSP frames and expands numeric textDocumentSync in the initialize response.
Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServer.cs Dev18-only wrapping of Pylance stdout with the initialize-response normalizing stream.
Python/Product/PythonTools/PythonTools.csproj Adds the new stream wrapper source file to the project compilation list.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

@@ -96,8 +96,13 @@ public async Task<Connection> ActivateAsync() {

if (!process.HasExited) {
// Create a connection where we wrap the stdin stream so that we can intercept all messages
Comment on lines +153 to +161
if (message["id"] == null || (message["result"] == null && message["error"] == null)) {
return frame;
}

_initializeResponseProcessed = true;
var textDocumentSync = message["result"]?["capabilities"]?["textDocumentSync"];
if (textDocumentSync?.Type != JTokenType.Integer) {
return frame;
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f5679231-48fb-4e6f-ba92-12102f084440
Copilot AI review requested due to automatic review settings August 4, 2026 00:42
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

Python/Product/PythonTools/PythonTools/LanguageServerClient/StreamHacking/PylanceInitializeResponseStream.cs:237

  • PylanceInitializeResponseStream owns a SemaphoreSlim (_readLock) but never disposes it. SemaphoreSlim is IDisposable and can hold unmanaged resources (via its wait handle), so leaving it undisposed can leak resources over the lifetime of VS sessions where the language client is restarted.
        protected override void Dispose(bool disposing) {
            if (disposing && Interlocked.Exchange(ref _disposed, 1) == 0) {
                _baseStream.Dispose();
            }
            base.Dispose(disposing);
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error list is not working. Some modules cannot be found during testing.

2 participants