Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@
"languages": [
{
"id": "cpp",
"extensions": [
".ccm",
".cppm",
".hip",
".ixx",
".sycl"
],
"filenames": [
"algorithm",
"any",
Expand Down
16 changes: 5 additions & 11 deletions Extension/src/Debugger/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
throw new Error("Default config not found in provideDebugConfigurations()");
}
const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (!editor || !util.isCppOrCFile(editor.document.uri) || configs.length <= 1) {
if (!editor || !util.isCppOrCFile(editor.document.uri, editor.document.languageId) || configs.length <= 1) {
return [defaultTemplateConfig];
}

Expand Down Expand Up @@ -546,22 +546,16 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
return;
}

const fileExt: string = path.extname(editor.document.fileName);
if (!fileExt) {
DebugConfigurationProvider.detectedBuildTasks = emptyTasks;
return;
}

// Don't offer tasks for header files.
const isHeader: boolean = util.isHeaderFile(editor.document.uri);
if (isHeader) {
DebugConfigurationProvider.detectedBuildTasks = emptyTasks;
return;
}

// Don't offer tasks if the active file's extension is not a recognized C/C++ extension.
const fileIsCpp: boolean = util.isCppFile(editor.document.uri);
const fileIsC: boolean = util.isCFile(editor.document.uri);
// Don't offer tasks if the active file is not a recognized C/C++ source file.
const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId);
const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId);
Comment thread
Colengms marked this conversation as resolved.
if (!(fileIsCpp || fileIsC)) {
DebugConfigurationProvider.detectedBuildTasks = emptyTasks;
return;
Expand Down Expand Up @@ -982,7 +976,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv

private async selectConfiguration(textEditor: vscode.TextEditor, pickDefault: boolean = true, onlyWorkspaceFolder: boolean = false): Promise<CppDebugConfiguration | undefined> {
const folder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(textEditor.document.uri);
if (!util.isCppOrCFile(textEditor.document.uri)) {
if (!util.isCppOrCFile(textEditor.document.uri, textEditor.document.languageId)) {
void vscode.window.showErrorMessage(localize("cannot.build.non.cpp", 'Cannot build and debug because the active file is not a C or C++ source file.'));
return;
}
Expand Down
48 changes: 33 additions & 15 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ManualPromise } from '../Utility/Async/manualPromise';
import { logAndReturn } from '../Utility/Async/returns';
import * as util from '../common';
import { isWindows } from '../constants';
import { FileTypeMappings, hasNativeFileTypeMappings, isTagParsableFile, resetFileTypeMappings, updateFileTypeMappings } from '../fileType';
import { instrument, isInstrumentationEnabled } from '../instrumentation';
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, logDebugProtocol, logLocalized, showWarning } from '../logger';
import { localizedStringCount, lookupString } from '../nativeStrings';
Expand Down Expand Up @@ -528,6 +529,7 @@ interface CppInitializationParams {

interface CppInitializationResult {
shouldShutdown: boolean;
fileTypeMappings?: FileTypeMappings;
}

interface TagParseStatus {
Expand Down Expand Up @@ -683,6 +685,7 @@ const ReportStatusNotification: NotificationType<ReportStatusNotificationBody> =
const DebugProtocolNotification: NotificationType<DebugProtocolParams> = new NotificationType<DebugProtocolParams>('cpptools/debugProtocol');
const DebugLogNotification: NotificationType<LocalizeStringParams> = new NotificationType<LocalizeStringParams>('cpptools/debugLog');
const CompileCommandsPathsNotification: NotificationType<CompileCommandsPaths> = new NotificationType<CompileCommandsPaths>('cpptools/compileCommandsPaths');
const FileTypeMappingsNotification: NotificationType<FileTypeMappings> = new NotificationType<FileTypeMappings>('cpptools/fileTypeMappings');
const ReferencesNotification: NotificationType<refs.ReferencesResult> = new NotificationType<refs.ReferencesResult>('cpptools/references');
const ReportReferencesProgressNotification: NotificationType<refs.ReportReferencesProgressNotification> = new NotificationType<refs.ReportReferencesProgressNotification>('cpptools/reportReferencesProgress');
const RequestCustomConfigs: NotificationType<RequestCustomConfigsParams> = new NotificationType<RequestCustomConfigsParams>('cpptools/requestCustomConfigs');
Expand Down Expand Up @@ -1406,6 +1409,7 @@ export class DefaultClient implements Client {

// Ideally this would be set earlier, but the task provider expects it to also mean that `this.innerConfiguration` is set.
this.languageClient.isStarted = true;
clients.ActiveClient.updateActiveDocumentTextOptions();

telemetry.logLanguageServerEvent("NonDefaultInitialCppSettings", this.settingsTracker.getUserModifiedSettings());
failureMessageShown = false;
Expand Down Expand Up @@ -1732,6 +1736,7 @@ export class DefaultClient implements Client {
localizedStrings: localizedStrings,
settings: this.getAllSettings()
};
resetFileTypeMappings();

this.loggingLevel = util.getNumericLoggingLevel(cppInitializationParams.settings.loggingLevel);
const lspInitializationOptions: LspInitializationOptions = {
Expand Down Expand Up @@ -1843,6 +1848,12 @@ export class DefaultClient implements Client {
// 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 client.sendRequest(InitializationRequest, cppInitializationParams);
if (initializeResult.fileTypeMappings) {
updateFileTypeMappings(initializeResult.fileTypeMappings);
} else {
resetFileTypeMappings();
}
DebugConfigurationProvider.ClearDetectedBuildTasks();

// If the server requested shutdown, then reload with the failsafe (null) client.
if (initializeResult.shouldShutdown) {
Expand Down Expand Up @@ -2589,6 +2600,11 @@ export class DefaultClient implements Client {
this.languageClient.onNotification(ReportStatusNotification, (e) => void this.updateStatus(e));
this.languageClient.onNotification(ReportTagParseStatusNotification, (e) => this.updateTagParseStatus(e));
this.languageClient.onNotification(CompileCommandsPathsNotification, (e) => void this.promptCompileCommands(e));
this.languageClient.onNotification(FileTypeMappingsNotification, (mappings) => {
updateFileTypeMappings(mappings);
DebugConfigurationProvider.ClearDetectedBuildTasks();
clients.ActiveClient.updateActiveDocumentTextOptions();
});
this.languageClient.onNotification(ReferencesNotification, (e) => this.processReferencesPreview(e));
this.languageClient.onNotification(ReportReferencesProgressNotification, (e) => this.handleReferencesProgress(e));
this.languageClient.onNotification(RequestCustomConfigs, (e) => this.handleRequestCustomConfigs(e));
Expand Down Expand Up @@ -2718,14 +2734,14 @@ export class DefaultClient implements Client {
void this.languageClient.sendNotification(FileCreatedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined);
});

// TODO: Handle new associations without a reload.
this.associations_for_did_change = new Set<string>(["cu", "cuh", "c", "i", "cpp", "cc", "cxx", "c++", "cp", "hpp", "hh", "hxx", "h++", "hp", "h", "ii", "ino", "inl", "ipp", "tcc", "txx", "tpp", "idl"]);
// Fallback for custom associations when native binaries do not publish effective file type mappings.
this.associations_for_did_change = new Set<string>();
const assocs: any = new OtherSettings().filesAssociations;
for (const assoc in assocs) {
const dotIndex: number = assoc.lastIndexOf('.');
if (dotIndex !== -1) {
const ext: string = assoc.substring(dotIndex + 1);
this.associations_for_did_change.add(ext);
this.associations_for_did_change.add(ext.toLowerCase());
}
}
this.rootPathFileWatcher.onDidChange(async (uri) => {
Expand All @@ -2739,17 +2755,19 @@ export class DefaultClient implements Client {
cachedEditorConfigLookups.clear();
this.updateActiveDocumentTextOptions();
}
if (dotIndex !== -1) {
const ext: string = uri.fsPath.substring(dotIndex + 1);
if (this.associations_for_did_change?.has(ext)) {
// VS Code has a bug that causes onDidChange events to happen to files that aren't changed,
// which causes a large backlog of "files to parse" to accumulate.
// We workaround this via only sending the change message if the modified time is within 10 seconds.
const mtime: Date = fs.statSync(uri.fsPath).mtime;
const duration: number = Date.now() - mtime.getTime();
if (duration < 10000) {
void this.languageClient.sendNotification(FileChangedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined);
}
const ext: string | undefined = dotIndex !== -1 ? uri.fsPath.substring(dotIndex + 1) : undefined;
const isTrackedFile: boolean = hasNativeFileTypeMappings()
? isTagParsableFile(uri.fsPath)
: isTagParsableFile(uri.fsPath) ||
(ext !== undefined && this.associations_for_did_change?.has(ext.toLowerCase()) === true);
if (isTrackedFile) {
// VS Code has a bug that causes onDidChange events to happen to files that aren't changed,
// which causes a large backlog of "files to parse" to accumulate.
// We workaround this via only sending the change message if the modified time is within 10 seconds.
const mtime: Date = fs.statSync(uri.fsPath).mtime;
const duration: number = Date.now() - mtime.getTime();
if (duration < 10000) {
void this.languageClient.sendNotification(FileChangedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined);
}
}
});
Expand Down Expand Up @@ -3079,7 +3097,7 @@ export class DefaultClient implements Client {
public updateActiveDocumentTextOptions(): void {
const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (editor && util.isCpp(editor.document)) {
void SessionState.buildAndDebugIsSourceFile.set(util.isCppOrCFile(editor.document.uri));
void SessionState.buildAndDebugIsSourceFile.set(util.isCppOrCFile(editor.document.uri, editor.document.languageId));
void SessionState.buildAndDebugIsFolderOpen.set(util.isFolderOpen(editor.document.uri));
// If using vcFormat, check for a ".editorconfig" file, and apply those text options to the active document.
const settings: CppSettings = new CppSettings(this.RootUri);
Expand Down
17 changes: 7 additions & 10 deletions Extension/src/LanguageServer/cppBuildTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,28 +73,23 @@ export class CppBuildTaskProvider implements TaskProvider {
return _task;
}

// Generate tasks to build the current file based on the user's detected compilers, the user's compilerPath setting, and the current file's extension.
// Generate tasks to build the current file based on the user's detected compilers, compilerPath setting, and file type.
public async getTasks(appendSourceToName: boolean = false): Promise<CppBuildTask[]> {
const editor: TextEditor | undefined = window.activeTextEditor;
const emptyTasks: CppBuildTask[] = [];
if (!editor) {
return emptyTasks;
}

const fileExt: string = path.extname(editor.document.fileName);
if (!fileExt) {
return emptyTasks;
}

// Don't offer tasks for header files.
const isHeader: boolean = util.isHeaderFile(editor.document.uri);
if (isHeader) {
return emptyTasks;
}

// Don't offer tasks if the active file's extension is not a recognized C/C++ extension.
const fileIsCpp: boolean = util.isCppFile(editor.document.uri);
const fileIsC: boolean = util.isCFile(editor.document.uri);
// Don't offer tasks if the active file is not a recognized C/C++ source file.
const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId);
const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId);
Comment thread
Colengms marked this conversation as resolved.
if (!(fileIsCpp || fileIsC)) {
return emptyTasks;
}
Expand Down Expand Up @@ -421,7 +416,9 @@ class CustomBuildTaskTerminal implements Pseudoterminal {
}

async openAsync(_initialDimensions: TerminalDimensions | undefined): Promise<void> {
if (this.buildOptions.taskUsesActiveFile && !util.isCppOrCFile(window.activeTextEditor?.document.uri)) {
if (this.buildOptions.taskUsesActiveFile && !util.isCppOrCFile(
window.activeTextEditor?.document.uri,
window.activeTextEditor?.document.languageId)) {
this.writeEmitter.fire(localize("cannot.build.non.cpp", 'Cannot build and debug because the active file is not a C or C++ source file.') + this.endOfLine);
this.closeEmitter.fire(-1);
return;
Expand Down
29 changes: 11 additions & 18 deletions Extension/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as nls from 'vscode-nls';
import { TargetPopulation } from 'vscode-tas-client';
import { ManualPromise } from './Utility/Async/manualPromise';
import { isWindows } from './constants';
import { classifyFilePath, FileTypeMapping } from './fileType';
import { getOutputChannelLogger, showOutputChannel } from './logger';
import { PlatformInformation } from './platform';
import * as Telemetry from './telemetry';
Expand Down Expand Up @@ -157,34 +158,26 @@ export function getVcpkgRoot(): string {
return vcpkgRoot;
}

/**
* This is a fuzzy determination of whether a uri represents a header file.
* For the purposes of this function, a header file has no extension, or an extension that begins with the letter 'h'.
* @param document The document to check.
*/
export function isHeaderFile(uri: vscode.Uri): boolean {
const fileExt: string = path.extname(uri.fsPath);
const fileExtLower: string = fileExt.toLowerCase();
return !fileExt || [".cuh", ".hpp", ".hh", ".hxx", ".h++", ".hp", ".h", ".inl", ".ipp", ".tcc", ".txx", ".tpp", ".tlh", ".tli", ""].some(ext => fileExtLower === ext);
return classifyFilePath(uri.fsPath)?.kind === 'header';
}

export function isCppFile(uri: vscode.Uri): boolean {
const fileExt: string = path.extname(uri.fsPath);
const fileExtLower: string = fileExt.toLowerCase();
return (fileExt === ".C") || [".cu", ".cpp", ".cc", ".cxx", ".c++", ".cp", ".ii", ".ino"].some(ext => fileExtLower === ext);
export function isCppFile(uri: vscode.Uri, languageId?: string): boolean {
const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId);
return fileType?.kind === 'source' && (fileType.language === 'cpp' || fileType.language === 'cuda');
}

export function isCFile(uri: vscode.Uri): boolean {
const fileExt: string = path.extname(uri.fsPath);
const fileExtLower: string = fileExt.toLowerCase();
return fileExt === ".c" || fileExtLower === ".i";
export function isCFile(uri: vscode.Uri, languageId?: string): boolean {
const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId);
return fileType?.kind === 'source' && fileType.language === 'c';
}

export function isCppOrCFile(uri: vscode.Uri | undefined): boolean {
export function isCppOrCFile(uri: vscode.Uri | undefined, languageId?: string): boolean {
if (!uri) {
return false;
}
return isCppFile(uri) || isCFile(uri);
const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId);
return fileType?.kind === 'source' && fileType.language !== undefined;
}

export function isFolderOpen(uri: vscode.Uri): boolean {
Expand Down
Loading
Loading