Compare commits

...
Author SHA1 Message Date
Bob Brown bdbac5714b Update some comments 2026-08-14 10:46:07 -07:00
Bob Brown 4d01799159 Merge branch 'main' into bobbrow/languageClientRefactor 2026-08-14 09:02:24 -07:00
dependabot[bot] d41ba04e26 Bump the github-actions group with 2 updates (#14678)
Bumps the github-actions group with 2 updates: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.4 to 4.37.6
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...5595ccaf912efad79be6eef63a5619ff05969be3)

Updates `github/codeql-action/analyze` from 4.37.4 to 4.37.6
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...5595ccaf912efad79be6eef63a5619ff05969be3)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-13 18:04:49 -07:00
Colen Garoutte-Carsonand@8prashant (Prashant Kumar Rai) 592661994a Update inactive regions to support column granularity (#14667)
Co-authored-by: @8prashant (Prashant Kumar Rai)
2026-08-13 13:41:59 -07:00
Sean McManus 98055e0aa8 Fix configuration path regex escaping (#14674) 2026-08-13 12:11:18 -07:00
Bob Brown dde8653da5 address PR feedback 2026-07-29 10:16:44 -07:00
Bob Brown 154f56e4c7 fix a bug found while testing 2026-07-29 09:50:11 -07:00
Bob Brown 95717b4eb4 self review 2026-07-29 08:59:36 -07:00
Bob Brown 2587d00a47 remove some more ready awaits 2026-07-28 17:27:45 -07:00
Bob Brown 77c1a14635 More cleanup 2026-07-28 17:05:14 -07:00
Bob Brown 07a4927c41 cleaning up and removing enqueue 2026-07-28 15:07:02 -07:00
Bob Brown 5351c00667 ensure that the language client is ready before all messages are sent 2026-07-27 17:15:42 -07:00
23 changed files with 228 additions and 262 deletions
+2 -2
View File
@@ -63,7 +63,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -91,7 +91,7 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:${{matrix.language}}"
@@ -16,7 +16,6 @@ nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFo
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
export class CopilotHoverProvider implements vscode.HoverProvider {
private client: DefaultClient;
private currentDocument: vscode.TextDocument | undefined;
private currentPosition: vscode.Position | undefined;
private currentCancellationToken: vscode.CancellationToken | undefined;
@@ -30,8 +29,8 @@ export class CopilotHoverProvider implements vscode.HoverProvider {
private chatModelId: string | undefined; // Save the selected model ID to avoid trying the same unavailable model repeatedly.
// Flag to avoid querying the LanguageModelChat repeatedly if no model is found
private checkedChatModel: boolean = false;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async getCachedChatModel(): Promise<vscode.LanguageModelChat | undefined> {
@@ -171,7 +170,6 @@ export class CopilotHoverProvider implements vscode.HoverProvider {
position: Position.create(position.line, position.character)
};
await this.client.ready;
if (this.currentCancellationToken?.isCancellationRequested) {
throw new vscode.CancellationError();
}
@@ -10,11 +10,10 @@ import { RequestCancelled, ServerCancelled } from '../protocolFilter';
import { CppSettings } from '../settings';
export class HoverProvider implements vscode.HoverProvider {
private client: DefaultClient;
private lastContent: vscode.MarkdownString[] | undefined;
private readonly hasContent = new ManualSignal<boolean>(true);
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | undefined> {
@@ -36,7 +35,6 @@ export class HoverProvider implements vscode.HoverProvider {
textDocument: { uri: document.uri.toString() },
position: Position.create(position.line, position.character)
};
await this.client.ready;
let hoverResult: vscode.Hover;
try {
hoverResult = await this.client.languageClient.sendRequest(HoverRequest, params, token);
@@ -214,15 +214,11 @@ export async function sendCallHierarchyCallsFromRequest(client: DefaultClient, i
export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
// Indicates whether a request is from an entry root node (e.g. top function in the call tree).
private isEntryRootNodeTelemetry: boolean = false;
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CallHierarchyItem | undefined> {
await this.client.ready;
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
workspaceReferences.clearViews();
@@ -261,7 +257,6 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
}
public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.CallHierarchyIncomingCall[] | undefined> {
await this.client.ready;
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
const CallHierarchyCallsToEvent: string = "CallHierarchyCallsTo";
@@ -316,8 +311,6 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
return undefined;
}
await this.client.ready;
const result: vscode.CallHierarchyOutgoingCall[] | undefined =
await sendCallHierarchyCallsFromRequest(this.client, item, token);
if (token.isCancellationRequested || result === undefined) {
@@ -40,9 +40,7 @@ export const GetCodeActionsRequest: RequestType<GetCodeActionsRequestParams, Get
new RequestType<GetCodeActionsRequestParams, GetCodeActionsResult, void>('cpptools/getCodeActions');
export class CodeActionProvider implements vscode.CodeActionProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
private static inlineMacroKind: vscode.CodeActionKind = vscode.CodeActionKind.RefactorInline.append("macro");
@@ -51,7 +49,6 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
public async provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise<(vscode.Command | vscode.CodeAction)[]> {
await this.client.ready;
let r: Range;
if (range instanceof vscode.Selection) {
if (range.active.isBefore(range.anchor)) {
@@ -11,9 +11,7 @@ import { CppSettings, OtherSettings } from '../settings';
import { makeVscodeTextEdits } from '../utils';
export class DocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
@@ -21,7 +19,6 @@ export class DocumentFormattingEditProvider implements vscode.DocumentFormatting
if (settings.formattingEngine === "disabled") {
return [];
}
await this.client.ready;
const filePath: string = document.uri.fsPath;
if (options.onChanges) {
let insertSpacesSet: boolean = false;
@@ -11,9 +11,7 @@ import { CppSettings } from '../settings';
import { makeVscodeTextEdits } from '../utils';
export class DocumentRangeFormattingEditProvider implements vscode.DocumentRangeFormattingEditProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range,
@@ -22,7 +20,6 @@ export class DocumentRangeFormattingEditProvider implements vscode.DocumentRange
if (settings.formattingEngine === "disabled") {
return [];
}
await this.client.ready;
const filePath: string = document.uri.fsPath;
const useVcFormat: boolean = settings.useVcFormat(document);
const configCallBack = async (editorConfigSettings: any | undefined) => {
@@ -58,14 +58,12 @@ export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider {
public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[] | vscode.DocumentSymbol[]> {
const client: Client = clients.getClientFor(document.uri);
if (client instanceof DefaultClient) {
const defaultClient: DefaultClient = <DefaultClient>client;
await client.ready;
const params: GetDocumentSymbolRequestParams = {
uri: document.uri.toString()
};
let response: GetDocumentSymbolResult;
try {
response = await defaultClient.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
response = await client.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
@@ -56,14 +56,10 @@ export async function sendFindAllReferencesRequest(client: DefaultClient, uri: v
}
export class FindAllReferencesProvider implements vscode.ReferenceProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideReferences(document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
await this.client.ready;
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
// Listen to a cancellation for this request. When this request is cancelled,
@@ -14,7 +14,6 @@ interface FoldingRangeRequestInfo {
}
export class FoldingRangeProvider implements vscode.FoldingRangeProvider {
private client: DefaultClient;
public onDidChangeFoldingRangesEvent = new vscode.EventEmitter<void>();
public onDidChangeFoldingRanges?: vscode.Event<void>;
@@ -22,12 +21,11 @@ export class FoldingRangeProvider implements vscode.FoldingRangeProvider {
// for the same file without waiting for the prior request to complete or cancelling them.
private pendingRequests: Map<string, FoldingRangeRequestInfo> = new Map<string, FoldingRangeRequestInfo>();
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
this.onDidChangeFoldingRanges = this.onDidChangeFoldingRangesEvent.event;
}
async provideFoldingRanges(document: vscode.TextDocument, context: vscode.FoldingContext, token: vscode.CancellationToken): Promise<vscode.FoldingRange[] | undefined> {
await this.client.ready;
const settings: CppSettings = new CppSettings();
if (!settings.codeFolding) {
return [];
@@ -11,9 +11,7 @@ import { CppSettings } from '../settings';
import { makeVscodeTextEdits } from '../utils';
export class OnTypeFormattingEditProvider implements vscode.OnTypeFormattingEditProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideOnTypeFormattingEdits(document: vscode.TextDocument, position: vscode.Position, ch: string, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
@@ -21,7 +19,6 @@ export class OnTypeFormattingEditProvider implements vscode.OnTypeFormattingEdit
if (settings.formattingEngine === "disabled") {
return [];
}
await this.client.ready;
const filePath: string = document.uri.fsPath;
const useVcFormat: boolean = settings.useVcFormat(document);
const configCallBack = async (editorConfigSettings: any | undefined) => {
@@ -18,14 +18,10 @@ const RenameRequest: RequestType<ReferencesParams, ReferencesResult, void> =
new RequestType<ReferencesParams, ReferencesResult, void>('cpptools/rename');
export class RenameProvider implements vscode.RenameProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string, _token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
await this.client.ready;
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
const settings: CppSettings = new CppSettings();
@@ -5,8 +5,7 @@
import * as vscode from 'vscode';
import { ManualPromise } from '../../Utility/Async/manualPromise';
interface FileData
{
interface FileData {
version: number;
promise: ManualPromise<vscode.SemanticTokens>;
tokenBuilder: vscode.SemanticTokensBuilder;
@@ -10,9 +10,7 @@ import { RequestCancelled, ServerCancelled } from '../protocolFilter';
import { makeVscodeLocation } from '../utils';
export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
private client: DefaultClient;
constructor(client: DefaultClient) {
this.client = client;
constructor(private client: DefaultClient) {
}
public async provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[]> {
+67 -183
View File
@@ -23,19 +23,16 @@ import { WorkspaceSymbolProvider } from './Providers/workspaceSymbolProvider';
// End provider imports
import { CodeSnippet, Trait } from '@github/copilot-language-server';
import { ok } from 'assert';
import * as fs from 'fs';
import * as os from 'os';
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { IntelliSenseStatus, Status } from 'vscode-cpptools/out/testApi';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, ResponseError, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, NotificationType, Position, Range, RequestType, ResponseError, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import * as rpc from 'vscode-languageclient/node';
import * as nls from 'vscode-nls';
import { DebugConfigurationProvider } from '../Debugger/configurationProvider';
import { ManualPromise } from '../Utility/Async/manualPromise';
import { ManualSignal } from '../Utility/Async/manualSignal';
import { logAndReturn } from '../Utility/Async/returns';
import { is } from '../Utility/System/guards';
import * as util from '../common';
import { isWindows } from '../constants';
import { instrument, isInstrumentationEnabled } from '../instrumentation';
@@ -60,6 +57,7 @@ import { CustomConfigurationProvider1, getCustomConfigProviders, isSameProviderE
import { DataBinding } from './dataBinding';
import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorConfig';
import { CppSourceStr, clients, configPrefix, initializeIntervalTimer, isWritingCrashCallStack, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
import { LanguageClient } from './languageClient';
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
import { PersistentFolderState, PersistentState, PersistentWorkspaceState } from './persistentState';
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
@@ -88,7 +86,7 @@ export function hasTrustedCompilerPaths(): boolean {
}
// Data shared by all clients.
let languageClient: LanguageClient;
const languageClient: LanguageClient = new LanguageClient();
let firstClientStarted: Promise<{ wasShutdown: boolean }>;
let languageClientHasCrashed: boolean = false;
let languageClientCrashedNeedsRestart: boolean = false;
@@ -221,11 +219,18 @@ interface FileChangedParams extends WorkspaceFolderParams {
uri: string;
}
interface InputRegion {
interface InputLineRange {
startLine: number;
endLine: number;
}
interface InputRegion {
startLine: number;
startColumn: number;
endLine: number;
endColumn: number;
}
interface DecorationRangesPair {
decoration: vscode.TextEditorDecorationType;
ranges: vscode.Range[];
@@ -374,7 +379,7 @@ export enum FoldingRangeKind {
export interface CppFoldingRange {
kind: FoldingRangeKind;
range: InputRegion;
range: InputLineRange;
}
export interface GetFoldingRangesResult {
@@ -781,7 +786,6 @@ class ClientModel {
export interface Client {
readonly ready: Promise<void>;
enqueue<T>(task: () => Promise<T>): Promise<T>;
InitializingWorkspaceChanged: vscode.Event<boolean>;
IndexingWorkspaceChanged: vscode.Event<boolean>;
ParsingWorkspaceChanged: vscode.Event<boolean>;
@@ -893,7 +897,6 @@ export function createNullClient(): Client {
}
export class DefaultClient implements Client {
private innerLanguageClient?: LanguageClient; // The "client" that launches and communicates with our language "server" process.
private disposables: vscode.Disposable[] = [];
private documentFormattingProviderDisposable: vscode.Disposable | undefined;
private formattingRangeProviderDisposable: vscode.Disposable | undefined;
@@ -909,7 +912,6 @@ export class DefaultClient implements Client {
private rootRealPath: string;
private workspaceStoragePath: string;
private trackedDocuments = new Map<string, vscode.TextDocument>();
private isSupported: boolean = true;
private inactiveRegionsDecorations = new Map<string, DecorationRangesPair>();
private settingsTracker: SettingsTracker;
private loggingLevel: number = 1;
@@ -934,20 +936,6 @@ export class DefaultClient implements Client {
private showConfigureIntelliSenseButton: boolean = false;
private pendingTagParsingCalls: PendingTagParsingCall[] = [];
/** A queue of asynchronous tasks that need to be processed befofe ready is considered active. */
private static queue = new Array<[ManualPromise<unknown>, () => Promise<unknown>] | [ManualPromise<unknown>]>();
/** returns a promise that waits initialization and/or a change to configuration to complete (i.e. language client is ready-to-use) */
private static readonly isStarted = new ManualSignal<void>(true);
/**
* Indicates if the blocking task dispatcher is currently running
*
* This will be in the Set state when the dispatcher is not running (i.e. if you await this it will be resolved immediately)
* If the dispatcher is running, this will be in the Reset state (i.e. if you await this it will be resolved when the dispatcher is done)
*/
private static readonly dispatching = new ManualSignal<void>();
// The "model" that is displayed via the UI (status bar).
private model: ClientModel = new ClientModel();
@@ -964,7 +952,7 @@ export class DefaultClient implements Client {
public get ReferencesCommandModeChanged(): vscode.Event<refs.ReferencesCommandMode> { return this.model.referencesCommandMode.ValueChanged; }
public get TagParserStatusChanged(): vscode.Event<string> { return this.model.parsingWorkspaceStatus.ValueChanged; }
public get ActiveConfigChanged(): vscode.Event<string> { return this.model.activeConfigName.ValueChanged; }
public isInitialized(): boolean { return this.innerLanguageClient !== undefined; }
public isInitialized(): boolean { return this.languageClient.isInitialized; }
public getShowConfigureIntelliSenseButton(): boolean { return this.showConfigureIntelliSenseButton; }
public setShowConfigureIntelliSenseButton(show: boolean): void { this.showConfigureIntelliSenseButton = show; }
@@ -999,10 +987,7 @@ export class DefaultClient implements Client {
}
public get languageClient(): LanguageClient {
if (!this.innerLanguageClient) {
throw new Error("Attempting to use languageClient before initialized");
}
return this.innerLanguageClient;
return languageClient;
}
public get configuration(): configs.CppProperties {
@@ -1376,19 +1361,18 @@ export class DefaultClient implements Client {
if (firstClientStarted === undefined || languageClientCrashedNeedsRestart) {
if (languageClientCrashedNeedsRestart) {
languageClientCrashedNeedsRestart = false;
// if we're recovering, the isStarted needs to be reset.
// if we're recovering, the isStarted needs to be reset
// because we're starting the first client again.
DefaultClient.isStarted.reset();
this.languageClient.isStarted = false;
this.languageClient.setLanguageClient(undefined);
}
firstClientStarted = this.createLanguageClient();
util.setProgress(util.getProgressExecutableStarted());
isFirstClient = true;
}
void this.init(rootUri, isFirstClient).catch(logAndReturn.undefined);
} catch (errJS) {
const err: NodeJS.ErrnoException = errJS as NodeJS.ErrnoException;
this.isSupported = false; // Running on an OS we don't support yet.
if (!failureMessageShown) {
failureMessageShown = true;
let additionalInfo: string;
@@ -1408,8 +1392,7 @@ export class DefaultClient implements Client {
ui = getUI();
ui.bind(this);
if ((await firstClientStarted).wasShutdown) {
this.isSupported = false;
DefaultClient.isStarted.resolve();
this.languageClient.isStarted = true;
return;
}
@@ -1421,7 +1404,10 @@ export class DefaultClient implements Client {
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
this.disposables.push(this.innerConfiguration);
this.innerLanguageClient = languageClient;
// Ideally this would be set earlier, but the task provider expects it to also mean that `this.innerConfiguration` is set.
this.languageClient.isStarted = true;
compilerDefaults = await this.requestCompiler();
telemetry.logLanguageServerEvent("NonDefaultInitialCppSettings", this.settingsTracker.getUserModifiedSettings());
failureMessageShown = false;
@@ -1488,7 +1474,6 @@ export class DefaultClient implements Client {
});
// The configurations will not be sent to the language server until the default include paths and frameworks have been set.
// The event handlers must be set before this happens.
compilerDefaults = await this.requestCompiler();
DefaultClient.updateClientConfigurations();
clients.forEach(client => {
if (client instanceof DefaultClient) {
@@ -1498,14 +1483,11 @@ export class DefaultClient implements Client {
});
}
} catch (err) {
this.isSupported = false; // Running on an OS we don't support yet.
if (!failureMessageShown) {
failureMessageShown = true;
void vscode.window.showErrorMessage(localize("unable.to.start", "Unable to start the C/C++ language server. IntelliSense features will be disabled. Error: {0}", String(err)));
}
}
DefaultClient.isStarted.resolve();
}
private getWorkspaceFolderSettings(workspaceFolderUri: vscode.Uri | undefined, workspaceFolder: vscode.WorkspaceFolder | undefined, settings: CppSettings, otherSettings: OtherSettings): WorkspaceFolderSettingsParams {
@@ -1693,7 +1675,7 @@ export class DefaultClient implements Client {
// from a sanitizer build to files in that directory (see getSanitizerServerEnv). This is
// undefined -- i.e. the environment is inherited unchanged -- for normal builds.
const sanitizerServerEnv: NodeJS.ProcessEnv | undefined = getSanitizerServerEnv();
const serverOptions: ServerOptions = {
const serverOptions: rpc.ServerOptions = {
run: { command: serverModule, options: { detached: false, cwd: util.getExtensionFilePath("bin"), env: sanitizerServerEnv } },
debug: { command: serverModule, args: [serverName], options: { detached: true, cwd: util.getExtensionFilePath("bin"), env: sanitizerServerEnv } }
};
@@ -1756,7 +1738,7 @@ export class DefaultClient implements Client {
loggingLevel: this.loggingLevel
};
const clientOptions: LanguageClientOptions = {
const clientOptions: rpc.LanguageClientOptions = {
documentSelector: [
{ scheme: 'file', language: 'c' },
{ scheme: 'file', language: 'cpp' },
@@ -1836,34 +1818,40 @@ export class DefaultClient implements Client {
// Refresh initializing state in UI.
this.model.isInitializingWorkspace.Value = true;
// Create the language client
languageClient = new LanguageClient(`cpptools`, serverOptions, clientOptions);
languageClient.onNotification(DebugProtocolNotification, logDebugProtocol);
languageClient.onNotification(DebugLogNotification, logLocalized);
languageClient.onNotification(LogTelemetryNotification, (e) => void this.logTelemetry(e));
languageClient.onNotification(ShowMessageWindowNotification, showMessageWindow);
languageClient.registerProposedFeatures();
await languageClient.start();
// Create the language client that spawns cpptools and configures RPC over stdin/stdout.
// This is the ONLY place outside of the LanguageClient wrapper where the VS Code LanguageClient class should be used directly.
// All other code should use the LanguageClient wrapper class.
const client = new rpc.LanguageClient(`cpptools`, serverOptions, clientOptions);
client.onNotification(DebugProtocolNotification, logDebugProtocol);
client.onNotification(DebugLogNotification, logLocalized);
client.onNotification(LogTelemetryNotification, (e) => void this.logTelemetry(e));
client.onNotification(ShowMessageWindowNotification, showMessageWindow);
client.registerProposedFeatures();
await client.start();
if (usesCrashHandler()) {
watchForCrashes(await languageClient.sendRequest(PreInitializationRequest, null));
watchForCrashes(await client.sendRequest(PreInitializationRequest, null));
} else if (os.platform() === "win32") {
const settings: CppSettings = new CppSettings();
if ((settings.windowsErrorReportingMode === "default" && !languageClientHasCrashed) ||
settings.windowsErrorReportingMode === "enabled") {
await languageClient.sendRequest(PreInitializationRequest, null);
await client.sendRequest(PreInitializationRequest, null);
}
}
// Move initialization to a separate message, so we can see log output from it.
// A request is used in order to wait for completion and ensure that no subsequent
// higher priority message may be processed before the Initialization request.
const initializeResult = await languageClient.sendRequest(InitializationRequest, cppInitializationParams);
const initializeResult = await client.sendRequest(InitializationRequest, cppInitializationParams);
// If the server requested shutdown, then reload with the failsafe (null) client.
if (initializeResult.shouldShutdown) {
await languageClient.stop();
await client.stop();
await clients.recreateClients(true);
} else {
// Don't set the inner language client on the wrapper until after initialization is complete.
// This ensures the order of the initialization messages.
languageClient.setLanguageClient(client);
}
return { wasShutdown: initializeResult.shouldShutdown };
@@ -1871,7 +1859,6 @@ export class DefaultClient implements Client {
public async sendDidChangeSettings(): Promise<void> {
// Send settings json to native side.
await this.ready;
await this.languageClient.sendNotification(DidChangeSettingsNotification, this.getAllSettings());
}
@@ -2211,7 +2198,6 @@ export class DefaultClient implements Client {
}
public async logDiagnostics(): Promise<void> {
await this.ready;
const response: GetDiagnosticsResult = await this.languageClient.sendRequest(GetDiagnosticsRequest, null);
const diagnosticsChannel: vscode.OutputChannel = getDiagnosticsChannel();
diagnosticsChannel.clear();
@@ -2279,16 +2265,12 @@ export class DefaultClient implements Client {
diagnosticsChannel.show(false);
}
public async rescanFolder(): Promise<void> {
await this.ready;
public rescanFolder(): Promise<void> {
return this.languageClient.sendNotification(RescanFolderNotification);
}
public async provideCustomConfigurations(docUris: vscode.Uri[], batchId: number): Promise<void> {
let isProviderRegistered: boolean = false;
const onFinished: () => void = () => {
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { batchId, isProviderRegistered });
};
try {
const providerId: string | undefined = this.configurationProvider;
if (!providerId) {
@@ -2302,7 +2284,7 @@ export class DefaultClient implements Client {
const resultCode = await this.provideCustomConfigurationAsync(docUris, provider);
telemetry.logLanguageServerEvent('provideCustomConfigurations', { providerId, resultCode });
} finally {
onFinished();
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { batchId, isProviderRegistered });
}
}
@@ -2522,9 +2504,8 @@ export class DefaultClient implements Client {
* the UI results and always re-requests (no caching).
*/
public async getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetIncludesResult> {
public getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetIncludesResult> {
const params: GetIncludesParams = { fileUri: uri.toString(), maxDepth };
await this.ready;
return this.languageClient.sendRequest(IncludesRequest, params);
}
@@ -2545,82 +2526,11 @@ export class DefaultClient implements Client {
}
/**
* a Promise that can be awaited to know when it's ok to proceed.
*
* This is a lighter-weight complement to `enqueue()`
*
* Use `await <client>.ready` when you need to ensure that the client is initialized, and to run in order
* Use `enqueue()` when you want to ensure that subsequent calls are blocked until a critical bit of code is run.
*
* This is lightweight, because if the queue is empty, then the only thing to wait for is the client itself to be initialized
* A Promise that can be awaited to know when the language client (cpptools) is up and running.
* It also implies that `this.innerConfiguration` is set.
*/
get ready(): Promise<void> {
if (!DefaultClient.dispatching.isCompleted || DefaultClient.queue.length) {
// if the dispatcher has stuff going on, then we need to stick in a promise into the queue so we can
// be notified when it's our turn
const p = new ManualPromise<void>();
DefaultClient.queue.push([p as ManualPromise<unknown>]);
return p;
}
// otherwise, we're only waiting for the client to be in an initialized state, in which case just wait for that.
return DefaultClient.isStarted;
}
/**
* Enqueue a task to ensure that the order is maintained. The tasks are executed sequentially after the client is ready.
*
* this is a bit more expensive than `.ready` - this ensures the task is absolutely finished executing before allowing
* the dispatcher to move forward.
*
* Use `enqueue()` when you want to ensure that subsequent calls are blocked until a critical bit of code is run.
* Use `await <client>.ready` when you need to ensure that the client is initialized, and still run in order.
*/
enqueue<T>(task: () => Promise<T>) {
ok(this.isSupported, localize("unsupported.client", "Unsupported client"));
// create a placeholder promise that is resolved when the task is complete.
const result = new ManualPromise<unknown>();
// add the task to the queue
DefaultClient.queue.push([result, task]);
// if we're not already dispatching, start
if (DefaultClient.dispatching.isSet) {
// start dispatching
void DefaultClient.dispatch();
}
// return the placeholder promise to the caller.
return result as Promise<T>;
}
/**
* The dispatch loop asynchronously processes items in the async queue in order, and ensures that tasks are dispatched in the
* order they were inserted.
*/
private static async dispatch() {
// reset the promise for the dispatcher
DefaultClient.dispatching.reset();
do {
// ensure that this is OK to start working
await this.isStarted;
// pick items up off the queue and run then one at a time until the queue is empty
const [promise, task] = DefaultClient.queue.shift() ?? [];
if (is.promise(promise)) {
try {
promise.resolve(task ? await task() : undefined);
} catch (e) {
console.log(e);
promise.reject(e);
}
}
} while (DefaultClient.queue.length);
// unblock anything that is waiting for the dispatcher to empty
this.dispatching.resolve();
return this.languageClient.ready;
}
private static async withLspCancellationHandling<T>(task: () => Promise<T>, token: vscode.CancellationToken): Promise<T> {
@@ -2672,7 +2582,7 @@ export class DefaultClient implements Client {
* listen for notifications from the language server.
*/
private registerNotifications(): void {
console.assert(this.languageClient !== undefined, "This method must not be called until this.languageClient is set in \"onReady\"");
console.assert(this.languageClient.isInitialized, "This method must not be called until the language client is initialized.");
this.languageClient.onNotification(ReloadWindowNotification, () => void util.promptForReloadWindowDueToSettingsChange());
this.languageClient.onNotification(UpdateTrustedCompilersNotification, (e) => void this.addTrustedCompiler(e.compilerPath));
@@ -2781,7 +2691,7 @@ export class DefaultClient implements Client {
* listen for file created/deleted events under the ${workspaceFolder} folder
*/
private registerFileWatcher(): void {
console.assert(this.languageClient !== undefined, "This method must not be called until this.languageClient is set in \"onReady\"");
console.assert(this.languageClient.isInitialized, "This method must not be called until the language client is initialized.");
if (this.rootFolder) {
// WARNING: The default limit on Linux is 8k, so for big directories, this can cause file watching to fail.
@@ -3054,7 +2964,8 @@ export class DefaultClient implements Client {
this.inactiveRegionsDecorations.set(uriString, currentSet);
}
Array.prototype.push.apply(currentSet.ranges, inactiveRegions.map(element => new vscode.Range(element.startLine, 0, element.endLine, 0)));
Array.prototype.push.apply(currentSet.ranges, inactiveRegions.map(element =>
new vscode.Range(element.startLine, element.startColumn, element.endLine, element.endColumn)));
// Apply the decorations to all *visible* text editors
const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => e.document.uri.toString() === uriString);
@@ -3142,18 +3053,16 @@ export class DefaultClient implements Client {
switchHeaderSourceFileName: fileName,
workspaceFolderUri: rootUri.toString()
};
return this.enqueue(async () => {
// Don't use withLspCancellationHandling() or withCancellation() here. If the switch target is already known,
// the caller should still be able to use it even if the progress notification was just cancelled.
try {
return await this.languageClient.sendRequest(SwitchHeaderSourceRequest, params, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
}
throw e;
// Don't use withLspCancellationHandling() or withCancellation() here. If the switch target is already known,
// the caller should still be able to use it even if the progress notification was just cancelled.
try {
return await this.languageClient.sendRequest(SwitchHeaderSourceRequest, params, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
}
});
throw e;
}
}
public async requestCompiler(newCompilerPath?: string): Promise<configs.CompilerDefaults> {
@@ -3233,7 +3142,6 @@ export class DefaultClient implements Client {
* send notifications to the language server to restart IntelliSense for the selected file.
*/
public async restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void> {
await this.ready;
return this.languageClient.sendNotification(RestartIntelliSenseForFileNotification, this.languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document)).catch(logAndReturn.undefined);
}
@@ -3250,7 +3158,6 @@ export class DefaultClient implements Client {
}
public async resetDatabase(): Promise<void> {
await this.ready;
return this.languageClient.sendNotification(ResetDatabaseNotification);
}
@@ -3262,29 +3169,24 @@ export class DefaultClient implements Client {
}
public async pauseParsing(): Promise<void> {
await this.ready;
return this.languageClient.sendNotification(PauseParsingNotification);
}
public async resumeParsing(): Promise<void> {
await this.ready;
return this.languageClient.sendNotification(ResumeParsingNotification);
}
public async PauseCodeAnalysis(): Promise<void> {
await this.ready;
this.model.isCodeAnalysisPaused.Value = true;
return this.languageClient.sendNotification(PauseCodeAnalysisNotification);
}
public async ResumeCodeAnalysis(): Promise<void> {
await this.ready;
this.model.isCodeAnalysisPaused.Value = false;
return this.languageClient.sendNotification(ResumeCodeAnalysisNotification);
}
public async CancelCodeAnalysis(): Promise<void> {
await this.ready;
return this.languageClient.sendNotification(CancelCodeAnalysisNotification);
}
@@ -3419,7 +3321,6 @@ export class DefaultClient implements Client {
currentConfiguration: index,
workspaceFolderUri: this.RootUri?.toString()
};
await this.ready;
await this.languageClient.sendNotification(ChangeSelectedSettingNotification, params);
let configName: string = "";
@@ -3435,7 +3336,6 @@ export class DefaultClient implements Client {
uri: vscode.Uri.file(path).toString(),
workspaceFolderUri: this.RootUri?.toString()
};
await this.ready;
return this.languageClient.sendNotification(ChangeCompileCommandsNotification, params);
}
@@ -3621,7 +3521,6 @@ export class DefaultClient implements Client {
const params: WorkspaceFolderParams = {
workspaceFolderUri: this.RootUri?.toString()
};
await this.ready;
return this.languageClient.sendNotification(ClearCustomConfigurationsNotification, params);
}
@@ -3630,7 +3529,6 @@ export class DefaultClient implements Client {
const params: WorkspaceFolderParams = {
workspaceFolderUri: this.RootUri?.toString()
};
await this.ready;
return this.languageClient.sendNotification(ClearCustomBrowseConfigurationNotification, params);
}
@@ -3717,7 +3615,6 @@ export class DefaultClient implements Client {
position: editor.selection.active,
next: next
};
await this.ready;
const response: Position | undefined = await this.languageClient.sendRequest(GoToDirectiveInGroupRequest, params);
if (response) {
const p: vscode.Position = new vscode.Position(response.line, response.character);
@@ -3750,7 +3647,6 @@ export class DefaultClient implements Client {
isCodeAction: codeActionArguments !== undefined,
isCursorAboveSignatureLine: codeActionArguments?.isCursorAboveSignatureLine
};
await this.ready;
const currentFileVersion: number | undefined = openFileVersions.get(params.uri);
if (currentFileVersion === undefined) {
return;
@@ -3796,23 +3692,19 @@ export class DefaultClient implements Client {
}
}
public async handleRunCodeAnalysisOnActiveFile(): Promise<void> {
await this.ready;
public handleRunCodeAnalysisOnActiveFile(): Promise<void> {
return this.languageClient.sendNotification(CodeAnalysisNotification, { scope: CodeAnalysisScope.ActiveFile });
}
public async handleRunCodeAnalysisOnOpenFiles(): Promise<void> {
await this.ready;
public handleRunCodeAnalysisOnOpenFiles(): Promise<void> {
return this.languageClient.sendNotification(CodeAnalysisNotification, { scope: CodeAnalysisScope.OpenFiles });
}
public async handleRunCodeAnalysisOnAllFiles(): Promise<void> {
await this.ready;
public handleRunCodeAnalysisOnAllFiles(): Promise<void> {
return this.languageClient.sendNotification(CodeAnalysisNotification, { scope: CodeAnalysisScope.AllFiles });
}
public async handleRemoveAllCodeAnalysisProblems(): Promise<void> {
await this.ready;
if (removeAllCodeAnalysisProblems()) {
return this.languageClient.sendNotification(CodeAnalysisNotification, { scope: CodeAnalysisScope.ClearSquiggles });
}
@@ -3843,8 +3735,6 @@ export class DefaultClient implements Client {
}
public async handleRemoveCodeAnalysisProblems(refreshSquigglesOnSave: boolean, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void> {
await this.ready;
// A deep copy is needed because the call to identifiers.splice below can
// remove elements in identifiersAndUris[...].identifiers.
const identifiersAndUrisCopy: CodeAnalysisDiagnosticIdentifiersAndUri[] = [];
@@ -4324,9 +4214,8 @@ export class DefaultClient implements Client {
}
public onInterval(): void {
// These events can be discarded until the language client is ready.
// Don't queue them up with this.notifyWhenLanguageClientReady calls.
if (this.innerLanguageClient !== undefined && this.configuration !== undefined) {
// These events can be discarded until the language client is ready. Don't queue them up.
if (this.languageClient.isInitialized && this.configuration !== undefined) {
void this.languageClient.sendNotification(IntervalTimerNotification).catch(logAndReturn.undefined);
this.configuration.checkCppProperties();
this.configuration.checkCompileCommands();
@@ -4372,8 +4261,6 @@ export class DefaultClient implements Client {
}
public async handleReferencesIcon(): Promise<void> {
await this.ready;
workspaceReferences.UpdateProgressUICounter(this.model.referencesCommandMode.Value);
// If the search is find all references, preview partial results.
@@ -4507,9 +4394,6 @@ class NullClient implements Client {
readonly ready: Promise<void> = Promise.resolve();
async enqueue<T>(task: () => Promise<T>) {
return task();
}
public get InitializingWorkspaceChanged(): vscode.Event<boolean> { return this.booleanEvent.event; }
public get IndexingWorkspaceChanged(): vscode.Event<boolean> { return this.booleanEvent.event; }
public get ParsingWorkspaceChanged(): vscode.Event<boolean> { return this.booleanEvent.event; }
@@ -121,7 +121,6 @@ export class ClientCollection {
const client: cpptools.Client = pair[1];
const newClient: cpptools.Client = this.createClient(client.RootFolder, true);
await newClient.ready;
for (const document of client.TrackedDocuments.values()) {
this.transferOwnership(document, client);
await newClient.sendDidOpen(document);
+2 -1
View File
@@ -4,10 +4,11 @@
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as vscode from 'vscode';
import { LanguageClient, NotificationType, Range } from 'vscode-languageclient/node';
import { NotificationType, Range } from 'vscode-languageclient';
import * as nls from 'vscode-nls';
import { Location, WorkspaceEdit } from './commonTypes';
import { CppSourceStr } from './extension';
import { LanguageClient } from './languageClient';
import { LocalizeStringParams, getLocalizedString } from './localization';
import { CppSettings } from './settings';
import { makeVscodeLocation, makeVscodeRange, makeVscodeTextEdits, rangeEquals } from './utils';
@@ -14,6 +14,7 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as which from 'which';
import { logAndReturn, returns } from '../Utility/Async/returns';
import { escapePathForSquiggles } from '../Utility/Text/escape';
import * as util from '../common';
import { isWindows } from '../constants';
import { getOutputChannelLogger } from '../logger';
@@ -135,7 +136,7 @@ export interface CompilerDefaults {
trustedCompilerFound: boolean;
}
export class CppProperties {
export class CppProperties implements vscode.Disposable {
private client: DefaultClient;
private rootUri: vscode.Uri | undefined;
private propertiesFile: vscode.Uri | undefined | null = undefined; // undefined and null values are handled differently
@@ -2119,10 +2120,8 @@ export class CppProperties {
continue;
}
// Escape the path string for literal use in a regular expression
// Need to escape any quotes to match the original text
let escapedPath: string = curPath.replace(/"/g, '\\"');
escapedPath = escapedPath.replace(/[-\"\/\\^$*+?.()|[\]{}]/g, '\\$&');
// Escape the parsed path for a literal regex match against its JSON spelling.
const escapedPath: string = escapePathForSquiggles(curPath);
// Create a pattern to search for the path with either a quote or semicolon immediately before and after,
// and extend that pattern to the next quote before and next quote after it.
+4 -12
View File
@@ -185,11 +185,11 @@ export async function activate(): Promise<void> {
disposables.push(vscode.workspace.onDidOpenTextDocument(onDidOpenTextDocument));
disposables.push(vscode.workspace.onDidChangeConfiguration(onDidChangeSettings));
disposables.push(vscode.window.onDidChangeTextEditorVisibleRanges((e) => clients.ActiveClient.enqueue(async () => onDidChangeTextEditorVisibleRanges(e))));
disposables.push(vscode.window.onDidChangeActiveTextEditor((e) => clients.ActiveClient.enqueue(async () => onDidChangeActiveTextEditor(e))));
disposables.push(vscode.window.onDidChangeTextEditorVisibleRanges(e => onDidChangeTextEditorVisibleRanges(e)));
disposables.push(vscode.window.onDidChangeActiveTextEditor(e => onDidChangeActiveTextEditor(e)));
ui.didChangeActiveEditor(); // Handle already active documents (for non-cpp files that we don't register didOpen).
disposables.push(vscode.window.onDidChangeTextEditorSelection((e) => clients.ActiveClient.enqueue(async () => onDidChangeTextEditorSelection(e))));
disposables.push(vscode.window.onDidChangeVisibleTextEditors((e) => clients.ActiveClient.enqueue(async () => onDidChangeVisibleTextEditors(e))));
disposables.push(vscode.window.onDidChangeTextEditorSelection(e => onDidChangeTextEditorSelection(e)));
disposables.push(vscode.window.onDidChangeVisibleTextEditors(e => onDidChangeVisibleTextEditors(e)));
updateLanguageConfigurations();
reportMacCrashes();
@@ -565,12 +565,10 @@ async function selectClient(): Promise<Client> {
}
async function onResetDatabase(): Promise<void> {
await clients.ActiveClient.ready;
return clients.ActiveClient.resetDatabase();
}
async function onRescanCompilers(sender?: any): Promise<void> {
await clients.ActiveClient.ready;
return clients.ActiveClient.rescanCompilers(sender);
}
@@ -579,7 +577,6 @@ async function onAddMissingInclude(): Promise<void> {
}
async function selectIntelliSenseConfiguration(sender?: any): Promise<void> {
await clients.ActiveClient.ready;
return clients.ActiveClient.promptSelectIntelliSenseConfiguration(sender);
}
@@ -877,7 +874,6 @@ async function onFindAllReferences(uri: vscode.Uri, position: vscode.Position, t
return undefined;
}
await client.ready;
const result = await sendFindAllReferencesRequest(client, uri, position, token ?? CancellationToken.None);
return result?.locations;
}
@@ -892,7 +888,6 @@ async function onGoToDefinition(uri: vscode.Uri, position: vscode.Position, toke
return undefined;
}
await client.ready;
return sendGoToDefinitionRequest(client, uri, position, token ?? CancellationToken.None);
}
@@ -906,7 +901,6 @@ async function onPrepareCallHierarchy(uri: vscode.Uri, position: vscode.Position
return undefined;
}
await client.ready;
return sendPrepareCallHierarchyRequest(client, uri, position, token ?? CancellationToken.None);
}
@@ -920,7 +914,6 @@ async function onCallHierarchyCallsTo(item: vscode.CallHierarchyItem, token?: vs
return undefined;
}
await client.ready;
return sendCallHierarchyCallsToRequest(client, item, token ?? CancellationToken.None);
}
@@ -934,7 +927,6 @@ async function onCallHierarchyCallsFrom(item: vscode.CallHierarchyItem, token?:
return undefined;
}
await client.ready;
return sendCallHierarchyCallsFromRequest(client, item, token ?? CancellationToken.None);
}
@@ -0,0 +1,90 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { Disposable } from 'vscode';
import { CancellationToken, NotificationHandler, NotificationType, RequestType } from 'vscode-languageclient';
import * as rpc from 'vscode-languageclient/node';
import { ManualSignal } from '../Utility/Async/manualSignal';
export class LanguageClient {
private _rpcClient?: rpc.LanguageClient;
private readonly _started = new ManualSignal<void>(true);
/**
* Returns a promise that waits for the RPC connection to be ready.
*/
public get ready(): Promise<void> {
return this._started;
}
/**
* Set the initialization state of the underlying RPC client.
* This is used to indicate that the underlying RPC client has been initialized and is ready to send requests.
* If resetting the RPC client, set this to false.
*/
public set isStarted(value: boolean) {
if (value) {
this._started.resolve();
} else {
this._started.reset();
}
}
/**
* Returns true if the underlying RPC client has been initialized.
* Used in cases where a quick check of the initialization state is desired instead of waiting on the `ready` promise.
*/
public get isInitialized(): boolean {
return !!this._rpcClient;
}
/**
* Set the underlying RPC client.
* This should only be called once during initialization.
*/
public setLanguageClient(languageClient?: rpc.LanguageClient): void {
this._rpcClient = languageClient;
}
/**
* Validate and return the underlying RPC client.
* Strips away the `undefined` type from `this._rpcClient`.
*/
private get rpcClient(): rpc.LanguageClient {
if (!this._rpcClient) {
throw new Error("Attempting to use languageClient before initialized");
}
return this._rpcClient;
}
public get protocol2CodeConverter(): rpc.LanguageClient["protocol2CodeConverter"] {
return this.rpcClient.protocol2CodeConverter;
}
public get code2ProtocolConverter(): rpc.LanguageClient["code2ProtocolConverter"] {
return this.rpcClient.code2ProtocolConverter;
}
public async sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Promise<R> {
await this.ready;
return this.rpcClient.sendRequest(type, params, token);
}
public async sendNotification<P>(type: NotificationType<P>, params?: P): Promise<void> {
await this.ready;
return this.rpcClient.sendNotification(type, params);
}
public onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): Disposable {
// These messages are received from cpptools, and therefore it is not needed to await the ready signal.
return this.rpcClient.onNotification(type, handler);
}
public stop(): Promise<void> {
return this._rpcClient?.stop() ?? Promise.resolve();
}
}
@@ -45,10 +45,8 @@ export function createProtocolFilter(): Middleware {
// client.takeOwnership() will call client.TrackedDocuments.add() again, but that's ok. It's a Set.
client.takeOwnership(document);
void sendMessage(document);
client.ready.then(() => {
const cppEditors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => util.isCpp(e.document));
client.onDidChangeVisibleTextEditors(cppEditors).catch(logAndReturn.undefined);
}).catch(logAndReturn.undefined);
const cppEditors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => util.isCpp(e.document));
void client.onDidChangeVisibleTextEditors(cppEditors).catch(logAndReturn.undefined);
}
}
},
+9
View File
@@ -0,0 +1,9 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
export function escapePathForSquiggles(s: string): string {
return s.replace(/[-"\/\\^$*+?.()|[\]{}]/g, (character: string): string =>
character === '"' ? '\\\\"' : `\\${character}`);
}
+32
View File
@@ -0,0 +1,32 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { describe, it } from 'mocha';
import { doesNotMatch, match, strictEqual } from 'node:assert';
import { escapePathForSquiggles } from '../../src/Utility/Text/escape';
describe('Text escaping', () => {
it('escapes paths for matching their JSON spelling', () => {
strictEqual(escapePathForSquiggles('"'), '\\\\"');
strictEqual(escapePathForSquiggles('\\'), '\\\\');
strictEqual(escapePathForSquiggles('.*'), '\\.\\*');
const parsedPath: string = String.raw`C:\src"quoted"`;
const sourcePath: string = String.raw`C:\src\"quoted\"`;
const pattern: RegExp = new RegExp(`^${escapePathForSquiggles(parsedPath)}$`);
match(sourcePath, pattern);
doesNotMatch(parsedPath, pattern);
});
it('handles repeated backslashes and regex metacharacters', () => {
const parsedPath: string = String.raw`C:\\sdk\\[headers]+(x)?.h\\say"hello"and"goodbye`;
const sourcePath: string = String.raw`C:\\sdk\\[headers]+(x)?.h\\say\"hello\"and\"goodbye`;
const pattern: RegExp = new RegExp(`^${escapePathForSquiggles(parsedPath)}$`);
match(sourcePath, pattern);
doesNotMatch(String.raw`C:\sdk\[headers]+(x)?.h\say\"hello\"and\"goodbye`, pattern);
doesNotMatch(String.raw`C:\\sdk\\headers+(x)?.h\\say\"hello\"and\"goodbye`, pattern);
});
});