Process resources after every change in XML files

This commit is contained in:
Akash Yadav
2021-09-14 18:24:26 +05:30
parent 3d5a35955e
commit 644bf97c0c
24 changed files with 249 additions and 115 deletions
@@ -109,12 +109,12 @@ public class DownloadActivity extends StudioActivity {
}
sb.append("echo 'Cleaning unsupported flags in binaries...' && $BUSYBOX find $JAVA_HOME -type f -exec androidide-cleaner {} \\; && ");
sb.append("echo " + DONE);
LOG.d("Installation commands ", sb.toString().replace("&&", "\n"));
LOG.debug("Installation commands ", sb.toString().replace("&&", "\n"));
server.bgAppend(sb.toString());
}
private void checkInstalled(CharSequence out) {
LOG.v("Installation output: ", out);
LOG.verbose("Installation output: ", out);
if(out != null) {
final String line = out.toString().trim();
runOnUiThread(() -> {
@@ -122,6 +122,7 @@ import java.util.Stack;
import java.util.regex.Pattern;
import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
import com.itsaky.androidide.models.SaveResult;
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
IDEService.BuildListener,
@@ -505,7 +506,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
} else if (id == R.id.menuEditor_lintRelease) {
getBuildService().lintRelease();
} else if (id == R.id.menuEditor_save) {
saveAll();
/**
* 1. Notify that all files are saved
* 2. If there were any XML files modified, call ':app:processDebugResources' task
*
* This will make sure that we generate view bindings and R.jar at proper time
* This will further result in updated code completion
*/
saveAll(true, true);
} else if (id == R.id.menuEditor_undo && this.mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
this.mCurrentFragment.undo();
} else if (id == R.id.menuEditor_redo && this.mCurrentFragment != null && this.mCurrentFragment.isVisible()) {
@@ -740,7 +748,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
@Override
public void saveFiles() {
saveAll();
saveAll(false, false);
}
// Can be called from a different different, better to run on UI thread
@@ -961,7 +969,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
File file = new File(params.uri);
if(!(file.exists() && file.isFile())) return;
diagnostics.put(file, params.diagnostics);
getDiagnosticsList().setAdapter(new DiagnosticsAdapter(mapAsGroup(diagnostics), this));
@@ -1158,7 +1165,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
openFileAndSelect(file, range);
}
} catch (Throwable th) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(th));
Logger.instance().error(ThrowableUtils.getFullStackTrace(th));
}
}
@@ -1228,7 +1235,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
);
results.put(file, matches);
} catch (Throwable th) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(th));
Logger.instance().error(ThrowableUtils.getFullStackTrace(th));
}
}
@@ -1521,11 +1528,32 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
getFileOptionsFragment(thisFile).show(getSupportFragmentManager(), TAG_FILE_OPTIONS_FRAGMENT);
}
private boolean saveAll() {
boolean isGradleSaved = mPagerAdapter.saveAll();
getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS);
if(isGradleSaved) notifySyncNeeded();
return isGradleSaved;
private boolean saveAll() {
return saveAll(true);
}
private boolean saveAll(boolean notify) {
return saveAll(notify, false);
}
private boolean saveAll(boolean notify, boolean canProcessResources) {
SaveResult result = mPagerAdapter.saveAll();
if(notify) {
getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS);
}
if(result.gradleSaved){
notifySyncNeeded();
}
LOG.info(
"saveAll()",
"notify:" + notify,
"processResources:" + canProcessResources,
"xmlSaved:" + result.xmlSaved,
"gradleSaved:" + result.gradleSaved);
if(result.xmlSaved && canProcessResources && getBuildService() != null) {
getBuildService().processDebugResources();
}
return result.gradleSaved;
}
private void handleOptionClick(SheetOption o) {
@@ -326,37 +326,37 @@ public class TerminalActivity extends StudioActivity {
@Override
public void logError(String tag, String message) {
LOG.e(message);
LOG.error(message);
}
@Override
public void logWarn(String tag, String message) {
LOG.w(message);
LOG.warn(message);
}
@Override
public void logInfo(String tag, String message) {
LOG.i(message);
LOG.info(message);
}
@Override
public void logDebug(String tag, String message) {
LOG.d(message);
LOG.debug(message);
}
@Override
public void logVerbose(String tag, String message) {
LOG.v(message);
LOG.verbose(message);
}
@Override
public void logStackTraceWithMessage(String tag, String message, Exception e) {
LOG.e(message, ThrowableUtils.getFullStackTrace(e));
LOG.error(message, e);
}
@Override
public void logStackTrace(String tag, Exception e) {
LOG.e(e.getMessage(), ThrowableUtils.getFullStackTrace(e));
LOG.error(e.getMessage(), e);
}
}
}
@@ -4,131 +4,146 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import com.itsaky.androidide.fragments.EditorFragment;
import com.itsaky.androidide.interfaces.JLSRequestor;
import com.itsaky.androidide.models.AndroidProject;
import io.github.rosemoe.editor.widget.CodeEditor;
import com.itsaky.androidide.models.SaveResult;
import com.itsaky.lsp.Range;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.itsaky.androidide.interfaces.JLSRequestor;
import com.itsaky.lsp.Range;
import com.itsaky.androidide.utils.Logger;
public class EditorPagerAdapter extends FragmentStatePagerAdapter
{
public class EditorPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<File> mOpenedFiles;
private ArrayList<Fragment> mFragments;
private AndroidProject project;
public EditorPagerAdapter(FragmentManager manager, AndroidProject project)
{
private static final Logger LOG = Logger.instance("EditorPagerAdapter");
public EditorPagerAdapter(FragmentManager manager, AndroidProject project) {
super(manager);
this.project = project;
this.mOpenedFiles = new ArrayList<>();
this.mFragments = new ArrayList<>();
}
public EditorPagerAdapter(FragmentManager manager, AndroidProject project, List<Fragment> fragments, List<File> files)
{
public EditorPagerAdapter(FragmentManager manager, AndroidProject project, List<Fragment> fragments, List<File> files) {
super(manager);
this.project = project;
this.mOpenedFiles = new ArrayList<>();
this.mFragments = new ArrayList<>();
mOpenedFiles.addAll(files);
mFragments.addAll(fragments);
}
public int findIndexOfEditorByFile(File file) {
for(int i=0;i<mFragments.size();i++) {
for (int i=0;i < mFragments.size();i++) {
Fragment frag = mFragments.get(i);
if(!(frag instanceof EditorFragment))
if (!(frag instanceof EditorFragment))
continue;
EditorFragment editor = (EditorFragment) frag;
if(editor.getFile() != null && editor.getFile().getAbsolutePath().equals(file.getAbsolutePath()))
if (editor.getFile() != null && editor.getFile().getAbsolutePath().equals(file.getAbsolutePath()))
return i;
}
return -1;
}
public EditorFragment findEditorByFile(File file) {
int index = findIndexOfEditorByFile(file);
if(index != -1)
if (index != -1)
return getFrag(index);
return null;
}
public int openFile(File file, Range selection, EditorFragment.FileOpenListener listener, JLSRequestor jlsRequestor)
{
public int openFile(File file, Range selection, EditorFragment.FileOpenListener listener, JLSRequestor jlsRequestor) {
int openedFileIndex = -1;
for(int i=0;i<mOpenedFiles.size();i++)
{
for (int i=0;i < mOpenedFiles.size();i++) {
File f = mOpenedFiles.get(i);
if(f.getAbsolutePath().equals(file.getAbsolutePath()))
{
if (f.getAbsolutePath().equals(file.getAbsolutePath())) {
openedFileIndex = i;
break;
}
}
if(openedFileIndex == -1)
{
if (openedFileIndex == -1) {
mFragments.add(EditorFragment
.newInstance(file, project, selection)
.setFileOpenListener(listener)
.setJLSRequestor(jlsRequestor));
.newInstance(file, project, selection)
.setFileOpenListener(listener)
.setJLSRequestor(jlsRequestor));
mOpenedFiles.add(file);
notifyDataSetChanged();
return mFragments.size() - 1;
} else return openedFileIndex;
}
public boolean saveAll() {
boolean hasGradle = false;
for(int i=0;i<mFragments.size();i++) {
hasGradle = hasGradle == false && save(i);
public SaveResult saveAll() {
SaveResult result = new SaveResult();
for (int i=0;i < mFragments.size();i++) {
save(i, result);
}
return hasGradle;
return result;
}
public boolean save(int index) {
if(index >= 0 && index < mFragments.size()) {
boolean hasGradle = false;
public void save(int index, SaveResult result) {
if (index >= 0 && index < mFragments.size()) {
EditorFragment frag = getFrag(index);
hasGradle = frag.isModified() && frag.getFile().getName().endsWith(EditorFragment.EXT_GRADLE);
/**
* Must be called before frag.save()
* Otherwise, it'll always return false
*/
final boolean modified = frag.isModified();
frag.save();
return hasGradle;
final boolean isGradle = frag.getFile().getName().endsWith(EditorFragment.EXT_GRADLE);
final boolean isXml = frag.getFile().getName().endsWith(EditorFragment.EXT_XML);
LOG.info("Save file: " + frag.getFile(),
"isModified:" + modified,
"isGradle:" + isGradle,
"isXml:" + isXml);
if (!result.gradleSaved) {
result.gradleSaved = modified && isGradle;
}
if (!result.xmlSaved) {
result.xmlSaved = modified && isXml;
}
}
return false;
}
public EditorFragment getFrag(int index) {
return (EditorFragment) getItem(index);
}
public List<Fragment> getFragments() {
return mFragments;
}
public List<File> getOpenedFiles() {
return mOpenedFiles;
}
@Override
public Fragment getItem(int p1)
{
public Fragment getItem(int p1) {
return p1 < 0 || p1 >= mFragments.size() ? null : mFragments.get(p1);
}
@Override
public int getCount()
{
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position)
{
return mOpenedFiles.get(position).getName();
public CharSequence getPageTitle(int position) {
String title = mOpenedFiles.get(position).getName();
if(getFrag(position).isModified())
title += "*";
return title;
}
}
@@ -89,7 +89,7 @@ public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH
final SpannableStringBuilder sb = new JavaHighlighter().highlight(match.line, match.match);
ThreadUtils.runOnUiThread(() -> binding.text.setText(sb));
} catch (Exception e) {
Logger.instance().e(ThrowableUtils.getFullStackTrace(e));
Logger.instance().error(ThrowableUtils.getFullStackTrace(e));
ThreadUtils.runOnUiThread(() -> binding.text.setText(match.match));
}
}).start();
@@ -353,7 +353,6 @@ public class EditorFragment extends BaseFragment implements EditorEventListener
@Override
public void onNewTextSet(CodeEditor editor) {
isModified = true;
}
@Override
@@ -113,7 +113,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
});
}
socket.close();
LOG.i("Socket disconnected");
LOG.info("Socket disconnected");
} catch (Throwable e) {
logEx(e);
}
@@ -268,7 +268,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
send.write(bodyBytes);
send.flush();
} catch (Throwable e) {
LOG.e("Error writing to server: " + ThrowableUtils.getFullStackTrace(e));
LOG.error("Error writing to server: " + ThrowableUtils.getFullStackTrace(e));
}
}).start();
}
@@ -285,7 +285,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
}
private void logEx(Throwable th) {
LOG.e(ThrowableUtils.getFullStackTrace(th));
LOG.error(ThrowableUtils.getFullStackTrace(th));
}
@Override
@@ -353,7 +353,7 @@ public class JavaLanguageServer implements ShellServer.Callback {
}
}
} catch (Throwable th) {
LOG.e("onServerOut", line, ThrowableUtils.getFullStackTrace(th));
LOG.error("onServerOut", line, ThrowableUtils.getFullStackTrace(th));
}
}
@@ -193,8 +193,8 @@ public class ToolsManager {
+ "\n }"
+ "\n "
+ "\n project.afterEvaluate {"
+ "\n project.tasks.getByName(\'assembleDebug\').finalizedBy(\'initializeIDEProject\')"
+ "\n project.tasks.getByName(\'assembleRelease\').finalizedBy(\'initializeIDEProject\')"
+ "\n project.tasks.getByName(\'compileDebugJavaWithJavac\').finalizedBy(\'initializeIDEProject\')"
+ "\n project.tasks.getByName(\'compileReleaseJavaWithJavac\').finalizedBy(\'initializeIDEProject\')"
+ "\n }"
+ "\n }"
+ "\n "
@@ -106,7 +106,7 @@ public class CompletionItemWrapper implements SuggestItem, Comparable {
CharPosition startPos = content.getIndexer().getCharPosition(start);
CharPosition endPos = content.getIndexer().getCharPosition(end);
Logger.instance("CompletionItemWrapper").d("PartialIdentifer Range: " + start + "," + end + ", text: " + content.subSequence(start, end));
Logger.instance("CompletionItemWrapper").debug("PartialIdentifer Range: " + start + "," + end + ", text: " + content.subSequence(start, end));
return new Range(new Position(startPos.line, startPos.column), new Position(endPos.line, endPos.column));
}
@@ -0,0 +1,24 @@
package com.itsaky.androidide.models;
/**
* Result obtained when files are saved
*/
public final class SaveResult {
/**
* Were any Gradle files saved?
*/
public boolean gradleSaved = false;
/**
* Were any XML files saved?
*/
public boolean xmlSaved = false;
public SaveResult() {}
public SaveResult(boolean gradleSaved, boolean xmlSaved) {
this.gradleSaved = gradleSaved;
this.xmlSaved = xmlSaved;
}
}
@@ -26,6 +26,12 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.itsaky.androidide.tasks.gradle.resources.ProcessDebugResources;
import com.itsaky.androidide.language.java.server.JavaLanguageServer;
import com.itsaky.lsp.DidChangeWatchedFilesParams;
import com.itsaky.lsp.FileEvent;
import com.itsaky.androidide.models.project.IDEModule;
import java.io.FileFilter;
import com.itsaky.lsp.FileChangeType;
public class IDEService implements ShellServer.Callback {
@@ -107,11 +113,13 @@ public class IDEService implements ShellServer.Callback {
listener.onBuildSuccessful(currentTask, charSequence.toString().trim());
appendOutputSeparator();
if(currentTask != null && currentTask instanceof ProcessDebugResources) {
if(currentTask != null && currentTask.affectsGeneratedSources()) {
/**
* An XML file was changed and saved
* Generaring R.jar
* If a task affects generated sources, we have to notify Java Language Server
*
* This is a must.
*/
notifyExternalSourceChange();
}
} else
if(charSequence.toString().contains(BUILD_FAILED)) {
@@ -180,6 +188,46 @@ public class IDEService implements ShellServer.Callback {
private String getString(@StringRes int id, Object... format) {
return app.getString(id, format);
}
/**
* Notifies about an external change event in generated .java files
* Generated source files can be:
* 1. ViewBinding/Databinding sources
* 2. BuildConfig
* We also notify Java language server to reload R.jar
*/
private void notifyExternalSourceChange() {
JavaLanguageServer server = app.getJavaLanguageServer();
if(mIDEProject == null || server == null) return;
server.didChangeWatchedFiles(new DidChangeWatchedFilesParams(createSourceChangeEvents()));
}
/**
* Returns a list of FileEvent representing the change in generated sources
*
* Takes care of adding changes from all modules
*/
private List<FileEvent> createSourceChangeEvents() {
final List<FileEvent> events = new ArrayList<>();
for(int i=0;i<mIDEProject.modules.size();i++) {
IDEModule module = mIDEProject.modules.get(i);
if(module == null
|| module.projectDir == null
|| module.projectDir.trim().length() <= 0)
continue;
File generated = new File(module.projectDir, "build/generated");
if(!(generated.exists() && generated.isDirectory())) continue;
final List<File> sources = FileUtils.listFilesInDirWithFilter(generated, JAVA_FILTER, true);
if(sources == null) continue;
for(File source : sources) {
events.add(new FileEvent(source.toURI(), FileChangeType.Changed));
}
}
return events;
}
private String getArguments(List<String> tasks) {
final PreferenceManager prefs = app.getPrefManager();
@@ -334,4 +382,18 @@ public class IDEService implements ShellServer.Callback {
void appendOutput(GradleTask task, CharSequence text);
void prepare();
}
/**
* This FileFilter is used to list java source files in 'generated' dir
*/
private static final FileFilter JAVA_FILTER = new FileFilter(){
@Override
public boolean accept(File p1) {
return p1.isFile()
&& !p1.isHidden()
&& p1.getName().endsWith(".java")
&& !p1.getName().equals("package-info.java");
}
};
}
@@ -31,20 +31,12 @@ public class ShellServer extends Thread {
ProcessBuilder processBuilder = new ProcessBuilder(new String[]{command});
processBuilder.directory(new File(dirPath));
processBuilder.redirectErrorStream(true);
processBuilder.environment().putAll(Environment.getEnvironment(false));
try {
this.process = processBuilder.start();
this.output = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("ln -sf /apex/com.android.runtime/lib64/bionic/libc.so $JAVA_HOME/lib/librt.so");
stringBuilder.append("\nln -sf /apex/com.android.runtime/lib64/bionic/libc.so $JAVA_HOME/lib/libpthread.so");
final Map<String, String> envs = Environment.getEnvironment(false);
for(Map.Entry<String, String> env : envs.entrySet()) {
stringBuilder.append(String.format("\nexport %s=%s", env.getKey(), env.getValue()));
}
append(stringBuilder.toString(), false);
} catch (Throwable th) {
LOG.e(ThrowableUtils.getFullStackTrace(th));
LOG.error(ThrowableUtils.getFullStackTrace(th));
if (callback != null) {
String out = ThrowableUtils.getFullStackTrace(th).concat("\n");
callback.output(out);
@@ -109,7 +101,7 @@ public class ShellServer extends Thread {
this.process.getOutputStream().write(str.concat("\n").getBytes());
this.process.getOutputStream().flush();
} catch (IOException e) {
LOG.e(ThrowableUtils.getFullStackTrace(e));
LOG.error(ThrowableUtils.getFullStackTrace(e));
}
}
@@ -44,7 +44,7 @@ public abstract class SocketConnection extends Thread {
mWriter.write(message.getBytes());
mWriter.flush();
} catch (Throwable e) {
LOG.e("Error writing to server: " + ex(e));
LOG.error("Error writing to server: " + ex(e));
}
}).start();
}
@@ -62,7 +62,7 @@ public abstract class SocketConnection extends Thread {
CloseUtils.closeIOQuietly(mWriter, mSocket);
onDisconnected();
} catch (Throwable e) {
LOG.e("Failed to connect to socket", ex(e));
LOG.error("Failed to connect to socket", ex(e));
onFailedToConnect(e);
}
}
@@ -4,6 +4,11 @@ import com.itsaky.androidide.services.IDEService;
public abstract class BaseGradleTask implements GradleTask {
@Override
public boolean affectsGeneratedSources() {
return false;
}
@Override
public boolean shouldSaveFiles() {
return getType() == Type.BUILD
@@ -9,6 +9,7 @@ public interface GradleTask {
public String getCommands();
public List<String> getTasks();
public int getTaskID();
public boolean affectsGeneratedSources();
public boolean shouldSaveFiles();
public boolean canOutput();
public boolean buildsApk();
@@ -9,7 +9,7 @@ import java.util.Arrays;
import java.util.List;
public class LintDebug extends BaseGradleTask {
@Override
public String getName() {
return StudioApp.getInstance().getString(R.string.lint_debug);
@@ -9,7 +9,7 @@ import java.util.Arrays;
import java.util.List;
public class LintRelease extends BaseGradleTask {
@Override
public String getName() {
return StudioApp.getInstance().getString(R.string.lint_release);
@@ -7,7 +7,7 @@ import java.util.List;
import java.util.Arrays;
public class InitializeIDEProject extends BaseGradleTask {
@Override
public String getName() {
return "initializeIDEProject";
@@ -8,6 +8,11 @@ import java.util.Arrays;
public class ProcessDebugResources extends BaseGradleTask {
@Override
public boolean affectsGeneratedSources() {
return true;
}
@Override
public String getName() {
return "processDebugResources";
@@ -94,7 +94,7 @@ public final class Environment {
}
return props;
} catch (Throwable th) {
LOG.e("Error reading properties", th);
LOG.error("Error reading properties", th);
return props;
}
}
@@ -14,7 +14,7 @@ public class Logger {
private static com.elvishew.xlog.Logger xLogger;
private static String TAG = "AndroidIDE";
public Logger w(Object... messages) {
public Logger warn(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
@@ -64,7 +64,7 @@ public class Logger {
return instance;
}
public Logger d(Object... messages) {
public Logger debug(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
@@ -82,7 +82,7 @@ public class Logger {
return this;
}
public Logger e(Object... messages) {
public Logger error(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
@@ -102,7 +102,7 @@ public class Logger {
return this;
}
public Logger v(Object... messages) {
public Logger verbose(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
@@ -120,7 +120,7 @@ public class Logger {
return this;
}
public Logger i(Object... messages) {
public Logger info(Object... messages) {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TAG);
@@ -4498,7 +4498,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
params.textDocument = new TextDocumentIdentifier(getFile().toURI());
jlsRequestor.findDefinition(params);
} catch (Throwable th) {
Logger.instance("CodeEditor").e(ThrowableUtils.getFullStackTrace(th));
Logger.instance("CodeEditor").error(ThrowableUtils.getFullStackTrace(th));
}
}
@@ -4511,7 +4511,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
params.textDocument = new TextDocumentIdentifier(getFile().toURI());
jlsRequestor.findReferences(params);
} catch (Throwable th) {
Logger.instance("CodeEditor").e(ThrowableUtils.getFullStackTrace(th));
Logger.instance("CodeEditor").error(ThrowableUtils.getFullStackTrace(th));
}
}
@@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<HorizontalScrollView
@@ -14,7 +14,6 @@
android:id="@+id/filetree_horizontalScrollView"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_marginTop="8dp"
android:fillViewport="true"/>
<ProgressBar
@@ -8,6 +8,10 @@ public class DidChangeWatchedFilesParams {
@SerializedName("changes")
public List<FileEvent> changes = new ArrayList<>();
public DidChangeWatchedFilesParams(List<FileEvent> changes) {
this.changes = changes;
}
public DidChangeWatchedFilesParams() {
changes = new ArrayList<>();