Allow other VS Code extensions to provide custom intellisense configurations.#1804
Conversation
WardenGnaw
left a comment
There was a problem hiding this comment.
Thank you for your contribution. I will wait on a full review until you mention it is ready.
I have just a few comments that can be addressed.
You can also ignore the
continuous-integration/appveyor/pr failure. That was a leftover artifact from an investigation on testing on Windows.
| "compilerOptions": { | ||
| "module": "commonjs", | ||
| "target": "es6", | ||
| "declaration": false, |
There was a problem hiding this comment.
The default value of declaration is false. Was there confusion that this needed to be included?
There was a problem hiding this comment.
Nope, I was experimenting and checked this in by accident :)
| @@ -38,7 +38,26 @@ | |||
| "Linters" | |||
There was a problem hiding this comment.
The file is rewritten when the extension activates. Please only check in changes you have made to the package.json, and not the extension's rewritten content.
|
@WardenGnaw thanks! I think this is ready to be reviewed. If you have questions regarding the background/purpose of this PR, feel free to contact me. #Closed |
|
@sherryyshi This seems to be mostly configurations from the Language Service. I will have @sean-mcmanus and @bobbrow to take a look at this PR. They may have questions about the purpose of these changes. Travis-CI is complaining about a couple errors. This it to make sure that the changes follow style guidelines, the project compiles, and it properly activates the extension for debugging. You can find more about the errors in the details link next to |
|
Bob said he was going to code review this. #Closed |
| * ------------------------------------------------------------------------------------------ */ | ||
| 'use strict'; | ||
|
|
||
| export interface ICppTools { |
There was a problem hiding this comment.
I [](start = 17, length = 1)
nit: we don't use Hungarian naming conventions in TypeScript. Please remove the 'I' from the interface name. #Closed
|
@sherryyshi, I sent you an email about this. Let's discuss over email or we can set up another meeting if necessary. #Closed |
| } | ||
|
|
||
| public sendCustomConfiguration(document: vscode.TextDocument, config: SourceFileConfiguration): void { | ||
| // TODO: Implement this |
There was a problem hiding this comment.
I would need some help on this.
There was a problem hiding this comment.
Take a look at the notifications defined at the top of this file under the "Notifications to the server" comment. You'll want to follow that pattern to create the JSON we'll want to send to the language server. It would probably be a vscode.Uri paired with a SourceFileConfiguration.
ChangeFolderSettingsNotification is probably the closest in function to what you will be doing. Then do
this.notifyWhenReady(() => this.languageClient.sendNotification(CustomConfigurationNotification, params));
You should move this method down under the "notifications to the language server" comment below. There are plenty of examples to emulate down there as well. #Closed
| @@ -1,3 +1,3 @@ | |||
| { | |||
| "intelliSenseEngine_default_percentage": 100 | |||
| { | |||
There was a problem hiding this comment.
revert changes to this file (did you remove the last newline?) #Closed
| defines: string[]; // This must also include the compiler default defines (_MSC_VER, __cplusplus, etc) | ||
| intelliSenseMode: string; // Probably just ‘msvc-x64’ for Windows | ||
| forcedInclude?: string[]; // Any files that need to be included before the source file is parsed | ||
| standard: string; // This is the C or C++ standard (language server will determine if the file is C or C++ based on this) |
There was a problem hiding this comment.
fix indentation (off by one) #Closed
|
|
||
| export interface SourceFileConfiguration { | ||
| includePaths: string[]; // This must also include the system include path (compiler defaults) | ||
| defines: string[]; // This must also include the compiler default defines (_MSC_VER, __cplusplus, etc) |
There was a problem hiding this comment.
remove _MSC_VER (Windows-specific) #Closed
| export interface SourceFileConfiguration { | ||
| includePaths: string[]; // This must also include the system include path (compiler defaults) | ||
| defines: string[]; // This must also include the compiler default defines (_MSC_VER, __cplusplus, etc) | ||
| intelliSenseMode: string; // Probably just ‘msvc-x64’ for Windows |
There was a problem hiding this comment.
Remove Windows-specific comment #Closed
| * ------------------------------------------------------------------------------------------ */ | ||
| 'use strict'; | ||
|
|
||
| import { CppToolsApi, CustomConfigurationProvider } from "./api"; |
There was a problem hiding this comment.
nit: we always use single quote ' for imports #Closed
|
|
||
| // If custom config provider exists, ask for the custom configuration and send a notification to the server | ||
| // before the textDocument/didOpen message so that the right information is used when the file is opened. | ||
| me.CustomConfigurationProvider.provideConfiguration(document.uri, (config: SourceFileConfiguration) => { |
There was a problem hiding this comment.
Make sure me.CustomConfigurationProvider is valid first. #Closed
| // If custom config provider exists, ask for the custom configuration and send a notification to the server | ||
| // before the textDocument/didOpen message so that the right information is used when the file is opened. | ||
| me.CustomConfigurationProvider.provideConfiguration(document.uri, (config: SourceFileConfiguration) => { | ||
| me.sendCustomConfiguration(document, config); // We have to add this method to client.ts |
There was a problem hiding this comment.
remove this comment (you already added the method) #Closed
|
|
||
| export interface CustomConfigurationProvider { | ||
| name: string; | ||
| provideConfiguration(uri: vscode.Uri, callback: (config: SourceFileConfiguration) => any): void; |
There was a problem hiding this comment.
Don't make this a callback interface. We need the answer immediately, so this method should just return it. #Closed
There was a problem hiding this comment.
Hmm... Our server needs to do this asynchronously because it's communication with an out-of-proc process vis json rpc... #Closed
There was a problem hiding this comment.
Making this async may result in vscode messages being sent out of order which will result in undefined behavior and confuse the language server. If you can't synchronize here because you think that your out of proc server is going to take too long to service the request, then we'll need to rethink this. Regardless of what we do here, sendMessage needs to be called before this method returns so you might need to block anyway unless we decide to let the language server open the file with the wrong config until your extension comes back with the correct one.
We probably need some kind of timeout and/or a try/catch block in here to ensure the safety of the provider call (in case a bad extension hangs or crashes) #Closed
| // before the textDocument/didOpen message so that the right information is used when the file is opened. | ||
| me.CustomConfigurationProvider.provideConfiguration(document.uri, (config: SourceFileConfiguration) => { | ||
| me.sendCustomConfiguration(document, config); // We have to add this method to client.ts | ||
| sendMessage(document); |
There was a problem hiding this comment.
This needs to come outside the callback. This message must always be sent. #Closed
|
|
||
| reportMacCrashes(); | ||
|
|
||
| eventEmitter.emit(activationCompleteEventId); |
There was a problem hiding this comment.
Having this event may be unnecessary if we just keep track of the providers in this file and send them down to the Client when they are available. I haven't thought this all the way through yet. I might be able to be more prescriptive once you get the UI part in for selecting providers. #Closed
…ging callback implementation for provideConfiguration
| Name: string = "(empty)"; | ||
| TrackedDocuments = new Set<vscode.TextDocument>(); | ||
| CustomConfigurationProvider: CustomConfigurationProvider; | ||
| CustomConcigurationProviders: CustomConfigurationProvider[] = []; |
| uri: document.uri.toString(), | ||
| configuration: config | ||
| }; | ||
| this.notifyWhenReady(() => this.languageClient.sendNotification(ChangeCompileCommandsNotification, params)); |
There was a problem hiding this comment.
ChangeCompileCommandsNotification [](start = 72, length = 33)
ChangeCompileCommandsNotification -> CustomConfigurationNotification #Closed
2a8775c to
440c304
Compare
…ith batched version(provideConfigurations).
| import * as vscode from 'vscode'; | ||
|
|
||
| export interface CppToolsApi { | ||
| registerCustomConfigurationProvider(provider: CustomConfigurationProvider): void; |
There was a problem hiding this comment.
registerCustomConfigurationProvider [](start = 4, length = 35)
Not blocking for this PR, but can you add comments in here (for each file/property/interface), especially for the API functions? Because this is the public contract for this extension, it should be well-documented
| * wait until the all pendingTasks are complete (e.g. language client is ready for use) | ||
| * before attempting to send messages | ||
| *************************************************************************************/ | ||
| public runBlockingTask(task: Thenable<any>): Thenable<any> { |
There was a problem hiding this comment.
any [](start = 42, length = 3)
Should this function be generic (i.e. accept type T), so this can be Thenable?
There was a problem hiding this comment.
| import { SourceFileConfiguration, CustomConfigurationProvider } from '../api'; | ||
| import { provideCustomConfiguration } from './extension'; | ||
|
|
||
| function runThenableWithTimeout(promise: Thenable<any>, ms: number): Thenable<any> { |
There was a problem hiding this comment.
runThenableWithTimeout [](start = 9, length = 22)
btw general comment: might be nice to have a utils.ts for the extension with common functions such as this being co-located.
Not blocking for this PR
| "onCommand:C_Cpp.ResumeParsing", | ||
| "onCommand:C_Cpp.ShowParsingCommands", | ||
| "onCommand:C_Cpp.TakeSurvey", | ||
| "onDebug" |
There was a problem hiding this comment.
I keep checking this file in by accident... :(
| import { SourceFileConfiguration, CustomConfigurationProvider } from '../api'; | ||
| import { provideCustomConfiguration } from './extension'; | ||
|
|
||
| function runThenableWithTimeout(promise: Thenable<any>, ms: number): Thenable<any> { |
There was a problem hiding this comment.
This should be removed. There is another copy of this in extension.ts which is what is actually being used.
|
|
||
| // Returns a race between our timeout and the passed in promise | ||
| return client.runBlockingTask(Promise.race([thenable(), timeout]).then((result: any) => { | ||
| clearTimeout(timer); |
There was a problem hiding this comment.
This line is unnecessary since the timer is cleared in the timeout promise. Will remove in the next push.
| const ChangeCompileCommandsNotification: NotificationType<FileChangedParams, void> = new NotificationType<FileChangedParams, void>('cpptools/didChangeCompileCommands'); | ||
| const ChangeSelectedSettingNotification: NotificationType<FolderSelectedSettingParams, void> = new NotificationType<FolderSelectedSettingParams, void>('cpptools/didChangeSelectedSetting'); | ||
| const IntervalTimerNotification: NotificationType<void, void> = new NotificationType<void, void>('cpptools/onIntervalTimer'); | ||
| const CustomConfigurationNotification: NotificationType<SourceFileConfigurationItem[], void> = new NotificationType<SourceFileConfigurationItem[], void>('cpptools/sendCustomConfiguration'); |
There was a problem hiding this comment.
Can you keep this as an interface instead of an array of items? It will make our life easier on the language server side. #Closed
| } | ||
|
|
||
| export function onDidCustomConfigurationChange(customConfigurationProvider: CustomConfigurationProvider): void { | ||
| // TODO: How to handle multiple workspaces? Only works with the current workspace. |
There was a problem hiding this comment.
multiple workspaces are handled by ClientCollection. You probably just need to forward this message to ClientCollection and have it iterate on its clients. #Closed
| } | ||
|
|
||
| let documents: vscode.Uri[] = []; | ||
| for (let i: number = 0; i < vscode.workspace.textDocuments.length; ++i) { |
There was a problem hiding this comment.
You're not going to want to do it this way. This doesn't have any notion of which workspace (read: Client) owns which files. Each Client tracks the files it owns, so you want to have each client request updates for its tracked documents and then send the updated configs to its language server. #Closed
|
|
||
| // TODO: Can we rely on clients.ActiveClient to be the client for the current workspace? | ||
| runBlockingThenableWithTimeout(() => customConfigurationProvider.provideConfigurations(documents, tokenSource.token), 1000, clients.ActiveClient, tokenSource) | ||
| .then((configs: SourceFileConfigurationItem[]) => { |
There was a problem hiding this comment.
i think you want to .then the runBlockingThenableWithTimeout call rather than the provideConfigurations call so that the language server notification doesn't count toward the timeout. #Closed
There was a problem hiding this comment.
There was a problem hiding this comment.
Ah! I got confused parsing the long line in my head. Can you reformat?
runBlockingThenableWithTimeout(() => {
return customConfigurationProvider.provideConfigurations(documents, tokenSource.token);
}, 1000, clients.ActiveClient, tokenSource)
``` #Closed
| timer = setTimeout(() => { | ||
| clearTimeout(timer); | ||
| if (tokenSource) { | ||
| tokenSource.cancel(); |
There was a problem hiding this comment.
I see you are marking the operation as cancelled, but I don't see any code that actually does anything about it. Are there future changes coming for this? #Closed
There was a problem hiding this comment.
The token is passed to our extension, and then passed to our "configuration server", which will react to it. Is there something to be done here as well?
There was a problem hiding this comment.
Not sure how I missed that. I see it now. #Closed
|
Looking through this, I've noticed that the system headers and system defines need to be included in the provided item. However, the code that grabbed those when passed in a compilerPath seems to be in the low level language service rather then accessable in typescript. Is there any way to get that added to the API? There are some uses for this where the build system doesn't inject the system included directly, but instead just depends on the compiler to have them. |
|
@ThadHouse, that's a reasonable request. |
| /** | ||
| * This must also include the system include path (compiler defaults) | ||
| */ | ||
| includePaths: string[]; |
There was a problem hiding this comment.
this should be renamed includePath to match c_cpp_properties.json
There was a problem hiding this comment.
@ThadHouse @bobbrow does this address the request?
There was a problem hiding this comment.
Does the low level language service cache values for the same compiler path?
This looks great, but I would worry about performance if it checked the compiler for every file, even with the same path.
There was a problem hiding this comment.
@ThadHouse, yes the language service will cache values to avoid invoking the compiler repeatedly. I am working on this change right now.
There was a problem hiding this comment.
One more question. The normal docs say skip the headers and the compilerPath is msvc mode is set. Is that still the case with this API?
There was a problem hiding this comment.
Which docs? Clang for Windows also requires msvc mode, so you should specify the compilerPath in all cases where you want the extension to add system includes and defines for you. We don't specifically query cl.exe in msvc mode yet since the mechanism isn't readily available (we read the registry instead), but we will in the future.
There was a problem hiding this comment.
https://github.com/Microsoft/vscode-cpptools/blob/master/Documentation/LanguageServer/FAQ.md
Specifically this one, where if you want to use MSVC it says don't provide a compilerPath, and only add a compilerPath if using clang for windows.

Create an API for registering configurations in the currently active language client.