Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file watcher for compile_commands.json and other configuration files #33

Merged
merged 11 commits into from
Sep 28, 2020

Conversation

rapgenic
Copy link
Contributor

In this pull request I added a simple file watcher that listens for events on the following files as per issue #6:

  • compile_commands.json
  • compile_flags.txt
  • .clang-tidy

Whenever one of these files is modified or created (but not empty to avoid false positives, such as tools that create an empty file before writing it) the clangd server is restarted. (I did not include the .clang-format file, because it seems to me that the clangd server does not need to be restarted whenever it is changed, to properly format a file)

As a side note, I understand that restarting the whole server might not be the best solution and that you may want to implement a cleaner refresh behavior in the clangd executable itself: however this pull request is very simple and may be easily got ridden of in a future when the server itself handles all the watching logic. That's why I opened it anyway, feel free to reject it if it does not meet your needs.

P.s.: This extension + clangd is an awesome work, thank you very much!

@ArekSredzki
Copy link

Looking forward to having this!

@sam-mccall
Copy link
Member

Sorry about the delay here. Yes, we're going to make clangd do this natively but this isn't done yet and the existing released versions aren't going away anytime soon. So this is really useful to have in some from. Once we implement this on the server, we can have it advertise a capability to disable this on the client.

I don't think unconditionally restarting clangd is necessarily the best thing. Can we make this a setting clangd.onConfigChanged with 3 options?

  • restart: behavior in this patch
  • ignore: existing behavior
  • prompt: notify the user of the changed files, and ask them whether to restart (using vscode.window.showInformationMessage)

I think prompt should be the default: ignore will make this feature too hard for users to discover, while restart will get us bug reports for sure because clangd will be glitchy (preamble needs to be rebuilt, index features will stop working until it's fully reloaded etc).

What do you think?

@rapgenic
Copy link
Contributor Author

rapgenic commented Jun 8, 2020

You're right, more choice is better.

I'll push a commit in the next hours/day. Given that the pull request for the restart command (#35) has been merged, I thought I could use that command to restart the server, if you're good with it.

@rapgenic rapgenic force-pushed the temp/filewatcher branch from 12d3f42 to cc0113b Compare June 8, 2020 21:52
src/extension.ts Outdated
if ((await vscode.workspace.fs.readFile(uri)).length) {
let relativePath = vscode.workspace.asRelativePath(uri, false);
// If it is at the top of the workspace
if (relativePath == path.basename(uri.fsPath)) {
Copy link

Choose a reason for hiding this comment

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

Why only listen to changes in the configuration file in the top of the workspace?
Currently clangd has been able to automatically find the compilation database located in the build folder.D78629

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That could be added easily. @sam-mccall are there any other default folders we should look into for file changes?

Copy link

Choose a reason for hiding this comment

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

I think we should delete this check.

  1. clangd has --compile-commands-dir option to specify any directory.
  2. clangd already supports multiple compilation databases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're making fair points, however I'm not sure those possibilities are that much used, so I don't know if they're worth the false positives we could get by removing this check (for example by having multiple build directories: if i compile my code in 3 different folders I don't want to have three messages popping up asking if I want to restart clang server).

Of course my opinion is a bit biased (the example I made is exactly why I implemented that check), so before removing/keeping it I'd rather listen to the maintainer opinion

Choose a reason for hiding this comment

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

I agree with @lh123 if we want to have a watch put on the compilation database this should be set by reading the parameter that is sent to clangd. There are numerous environments where compile_commands.json resides in other directories other than the top director or the build directory. For example @mozilla we use dedicated obj directory that can be named whatever you like and the compile_commands.json can be created in that directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi and sorry for the delay. From your comments I see that i might have underestimated the utility of that option... I'll try to push some new commits in the next days (right now I do not have my pc near me)

@abpostelnicu
Copy link

Sorry about the delay here. Yes, we're going to make clangd do this natively but this isn't done yet and the existing released versions aren't going away anytime soon. So this is really useful to have in some from. Once we implement this on the server, we can have it advertise a capability to disable this on the client.

I don't think unconditionally restarting clangd is necessarily the best thing. Can we make this a setting clangd.onConfigChanged with 3 options?

  • restart: behavior in this patch

  • ignore: existing behavior

  • prompt: notify the user of the changed files, and ask them whether to restart (using vscode.window.showInformationMessage)

I think prompt should be the default: ignore will make this feature too hard for users to discover, while restart will get us bug reports for sure because clangd will be glitchy (preamble needs to be rebuilt, index features will stop working until it's fully reloaded etc).

What do you think?

Is there a phabricator issue on llvm about the implementation of this sort of feature on the server side? I would be more than happy to contribute to it.

@Trass3r
Copy link
Contributor

Trass3r commented Aug 21, 2020

No nothing yet, clangd/clangd#83

@rapgenic
Copy link
Contributor Author

I agree with @lh123 if we want to have a watch put on the compilation database this should be set by reading the parameter that is sent to clangd.

I implemented this option as discussed, please tell me if it works as intended.

I wanted to note that since I do not have access to a windows/mac environment I have not been able to test those platforms, so anyone providing feedback would be welcome!

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

Thanks, the PR looks nice, a few nits, will try to play around it closely.

src/extension.ts Outdated Show resolved Hide resolved
src/extension.ts Outdated Show resolved Hide resolved
src/extension.ts Outdated Show resolved Hide resolved
@rapgenic
Copy link
Contributor Author

rapgenic commented Sep 9, 2020

@hokein I implemented what you suggested! Everything is now on a new file and there should be no debug code left...

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

Thanks, I got a chance to play around it, it works nicely. I think we can simplify the patch a bit more.

btw, we plan to make clangd support it natively in Q4 -- it would be available in clangd 12 hopefully.

src/config-file-watcher.ts Outdated Show resolved Hide resolved
src/config-file-watcher.ts Outdated Show resolved Hide resolved
src/config-file-watcher.ts Outdated Show resolved Hide resolved
src/config-file-watcher.ts Outdated Show resolved Hide resolved
}

async handleConfigFilesChanged(uri: vscode.Uri) {
if ((await vscode.workspace.fs.readFile(uri)).length) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

unclear what's the purpose of this if? to check the file is not empty? can we add a comment here?

Copy link
Contributor

Choose a reason for hiding this comment

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

It shouldn't read the whole file for that either.

Suggested change
if ((await vscode.workspace.fs.readFile(uri)).length) {
if ((await vscode.workspace.fs.stat(uri)).size) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, early exit!

Suggested change
if ((await vscode.workspace.fs.readFile(uri)).length) {
if ((await vscode.workspace.fs.stat(uri)).size <= 0)
return;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hokein this is to check, as you understood, if the file is empty, because I have noticed that sometimes the tools that generate the compile_commands.json file, before writing to it they clear it or they delete it and create a new empty one, and this should not toggle a restart of the server.

@Trass3r much better solution thanks!

let restart = false;

if (baseName == 'compile_commands.json') {
if (this.compileCommandsFolder &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Things become complicated with --compile-commands-dir:

  • --compile-commands-dir accepts a relative or an absolute file path;
  • vscode's createFileSystemWatcher only watches files within the workspace folder;
  1. if compile-commands-dir is an absolute file path:
    a. the absolute file path is in the workspace folder -- this might be the case we want to support, but the current code is not correct;
    b. the absolute file path is not in the workspace folder -- we can't do anything about this case, as watching files outside of the workspace folder is not possible in vscode;

  2. if compile-commands-dir is a relative file path, clangd will use its cwd to form an absolute file path internally, I'm not sure we have a stable way to have a share view of cwd between clangd and vscode.

Given the complexity above, and the fact that compile-commands-dir is probably not a major use case, I'd suggest just not supporting it.

else if (!this.compileCommandsFolder && relativePath == baseName)
restart = true;
} else {
if (relativePath == baseName)
Copy link
Collaborator

Choose a reason for hiding this comment

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

if my reading is correct, we're trying to watch $workspacefolder/{compile_commands.json,compile_flags.txt,.clang-tidy}, and skip any config files in subfolders, e.g. $workspacefolder/dir1/compile_command.json would be skipped.

if so, I think we can do it in a simpler way rather than doing a post filtering vscode.workspace.createFileSystemWatcher( new RelativePattern(vscode.workspace.rootPath, '{compile_commands.json,compile_flags.txt,.clang-tidy}'));

Copy link
Contributor

Choose a reason for hiding this comment

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

  • Agreed, it doesn't make sense to install a recursive file watcher if you're only interested in the root folder.
  • Anyway the compile commands file is often in a build subfolder which would be found by clangd but ignored by this code it seems.
  • There could also be multiple subfolders for different configs like buildDebug, buildRelease, ... I'm using this together with the CMake Tools copy commands.json to base folder feature to make clangd work without explicit --compile-commands-dir.
  • Maybe it would be enough to watch only files up to 1 directory level deep in general. Anything else seems out of scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hokein @Trass3r the main reason for using a recursive file watcher was because some users asked to support the compile-commands-dir option, which meant that the file could be in any subdirectory (even though it could still be made with the RelativePattern method suggested by @hokein).

The other reason however is that when multi folder workspaces are opened, looking only in the workspace.rootPath would not be correct. Using a generic watcher instead notifies a file change in all folders, but the small price to pay is to check if the path is the right one.

There could also be multiple subfolders for different configs like buildDebug, buildRelease

These however wouldn't be automatically recognised by clangd or would they? (Can we clarify what folders clangd looks for the compilation database except for the root of the project and the build folder?)

Of course were we to drop support for the compile-commands-dir option the code would be simpler, but I still think that a recursive watcher would be needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

As I said I let CMake Tools copy the commands.json to the base folder after configuration finished.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I think we don't need to watch for other folders, because the event is triggered when the file is copied in the base folder. Unless clangd looks for compile_commands.json in more folders than I am aware of,

  • project's root folder
  • build

should be the only directories we need to watch into

Copy link
Collaborator

Choose a reason for hiding this comment

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

this sounds good to me, it should cover most major cases.

A minor issue is that $workspacefolder/compile_commands.json is usually a symlink of $workspacefolder/build/compile_commands.json, which means if we change one of them, we will get two prompt-up dialogs, which seems a bit annoying, but I don't have better suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the events triggers on build/compile_commands.json we could check if it exists a compile_commands.json in the root folder too and if it's the case, simply ignore the event...

Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't bother. I'm affected by this too but I'd just pick "yes always" and be done with it.

src/config-file-watcher.ts Outdated Show resolved Hide resolved
}

async handleConfigFilesChanged(uri: vscode.Uri) {
if ((await vscode.workspace.fs.readFile(uri)).length) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It shouldn't read the whole file for that either.

Suggested change
if ((await vscode.workspace.fs.readFile(uri)).length) {
if ((await vscode.workspace.fs.stat(uri)).size) {

}

async handleConfigFilesChanged(uri: vscode.Uri) {
if ((await vscode.workspace.fs.readFile(uri)).length) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, early exit!

Suggested change
if ((await vscode.workspace.fs.readFile(uri)).length) {
if ((await vscode.workspace.fs.stat(uri)).size <= 0)
return;

else if (!this.compileCommandsFolder && relativePath == baseName)
restart = true;
} else {
if (relativePath == baseName)
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Agreed, it doesn't make sense to install a recursive file watcher if you're only interested in the root folder.
  • Anyway the compile commands file is often in a build subfolder which would be found by clangd but ignored by this code it seems.
  • There could also be multiple subfolders for different configs like buildDebug, buildRelease, ... I'm using this together with the CMake Tools copy commands.json to base folder feature to make clangd work without explicit --compile-commands-dir.
  • Maybe it would be enough to watch only files up to 1 directory level deep in general. Anything else seems out of scope.

@rapgenic
Copy link
Contributor Author

I think I fixed everything you asked.

Regarding the file watcher I made the following choices:

  • drop support for compile-commands-dir option
  • Look only on the base folder for all the configuration files and build/compile_commands.json

(The watcher is still recursive because of what I said before: when multiple folders are open there is no way that i know of to instanciate a file watcher only on the root of every folder, and to update it whenever a folder is added or removed)

As you see I have chosen the easy way: that's because as you said this will be a feature present in clangd in the near future, so in my opinion there is no need to make this feature overcomplicated considering that it is temporary.

@Trass3r
Copy link
Contributor

Trass3r commented Sep 26, 2020

The watcher is still recursive because of what I said before: when multiple folders are open there is no way that i know of to instantiate a file watcher only on the root of every folder, and to update it whenever a folder is added or removed

I see, I haven't worked with multi root workspaces yet.
https://github.com/microsoft/vscode/wiki/Adopting-Multi-Root-Workspace-APIs#eliminating-rootpath
So I guess something like

createFileSystemWatcher('{' + workspace.workspaceFolders.map(f => f.uri.fsPath).join(',') + '}/compile_commands.json')

could work. But probably not worth the hassle as you said.

@rapgenic
Copy link
Contributor Author

Also that would not account for the fact that workspace folders can be added and removed, so we would need to listen for such an event and the file watcher would need to be recreated every time this happened..

@Trass3r
Copy link
Contributor

Trass3r commented Sep 27, 2020

Yep there's an event for just that.

@rapgenic
Copy link
Contributor Author

Well in the end I gave it a try... :) Might be useful on larger projects

@Trass3r
Copy link
Contributor

Trass3r commented Sep 27, 2020

Interesting so my quickly hacked together code actually worked 😁

Copy link
Collaborator

@hokein hokein left a comment

Choose a reason for hiding this comment

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

Thanks! It looks nice.

src/config-file-watcher.ts Outdated Show resolved Hide resolved
@hokein hokein merged commit aed58f9 into clangd:master Sep 28, 2020
@hokein
Copy link
Collaborator

hokein commented Sep 28, 2020

@rapgenic, thanks for the contribution!

@ghost
Copy link

ghost commented Nov 6, 2020

@hokein Is there any ETA for the next release of the extension? The file watcher is highly anticipated in our team :)

@hokein
Copy link
Collaborator

hokein commented Nov 9, 2020

@JanHildenbrandBosch I'll make a new release this week.

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.

7 participants