diff --git a/CHANGELOG.md b/CHANGELOG.md index d037f025b..0b54d476b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,7 @@ This is the changelog of all the releases of AndroidIDE. - Code formatter for Java source code. - Support for - [`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView) - , + [`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView), [`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton), [`Switch`](https://developer.android.com/reference/android/widget/Switch), [`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout) in layout diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index d1aab8308..94e98b694 100755 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.LinearLayout; + import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -50,6 +51,7 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.transition.Slide; import androidx.transition.TransitionManager; + import com.blankj.utilcode.util.ClipboardUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.IntentUtils; @@ -115,8 +117,9 @@ import com.itsaky.lsp.models.InitializeParams; import com.itsaky.lsp.models.Range; import com.itsaky.toaster.Toaster; import com.unnamed.b.atv.model.TreeNode; -import io.github.rosemoe.sora.event.ContentChangeEvent; -import io.github.rosemoe.sora.event.Unsubscribe; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -131,9 +134,11 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.regex.Pattern; import java.util.stream.Collectors; + +import io.github.rosemoe.sora.event.ContentChangeEvent; +import io.github.rosemoe.sora.event.Unsubscribe; import me.piruin.quickaction.ActionItem; import me.piruin.quickaction.QuickAction; -import org.jetbrains.annotations.Contract; public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener, @@ -708,7 +713,10 @@ public class EditorActivity extends StudioActivity mBinding.editorContainer.setDisplayedChild(position); final var editorView = getEditorAtIndex(position); - mViewModel.setCurrentFile(position, Objects.requireNonNull(editorView).getFile()); + Objects.requireNonNull(editorView); + + editorView.onEditorSelected(); + mViewModel.setCurrentFile(position, editorView.getFile()); refreshSymbolInput(editorView); invalidateOptionsMenu(); } diff --git a/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java b/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java new file mode 100644 index 000000000..e6186d481 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java @@ -0,0 +1,47 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.language; + +import androidx.annotation.NonNull; + +import com.itsaky.lsp.models.DiagnosticItem; + +import java.util.List; + +/** + * Base class for analyze managers. + * + * @author Akash Yadav + */ +public interface IAnalyzeManager { + + /** + * Update the diagnostic items. + * + * @param diagnostics The new list of diagnostics. + */ + default void updateDiagnostics(@NonNull List diagnostics) {} + + /** + * Get the diagnostic items. + * + * @return The diagnostic items. Must not be null. + */ + @NonNull + List getDiagnostics(); +} diff --git a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java index 70c529a02..e68608b3e 100644 --- a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java @@ -17,13 +17,17 @@ package com.itsaky.androidide.language; import androidx.annotation.NonNull; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.DiagnosticItem; -import io.github.rosemoe.sora.lang.Language; + +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import io.github.rosemoe.sora.lang.Language; + /** * Base class for language implementations in the IDE. * @@ -50,6 +54,18 @@ public abstract class IDELanguage implements Language { return Collections.emptyList(); } + public void setDiagnostics(List diagnostics) { + if (diagnostics == null) { + diagnostics = new ArrayList<>(0); + } + + final var analyzer = getAnalyzeManager(); + if (analyzer instanceof IAnalyzeManager) { + ((IAnalyzeManager) analyzer).updateDiagnostics(diagnostics); + analyzer.rerun(); + } + } + @Override public CharSequence format(CharSequence text) { final var server = getLanguageServer(); diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java index ca081fd45..2c101c6f6 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java @@ -28,50 +28,51 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get; import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.withoutCompletion; import androidx.annotation.NonNull; + +import com.itsaky.androidide.language.IAnalyzeManager; import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.utils.CharSequenceReader; import com.itsaky.androidide.utils.Logger; -import com.itsaky.androidide.views.editor.IDEEditor; -import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.models.DiagnosticItem; -import com.itsaky.lsp.models.DiagnosticResult; import com.itsaky.lsp.models.DiagnosticSeverity; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.Token; +import org.jetbrains.annotations.Contract; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; import io.github.rosemoe.sora.lang.styling.CodeBlock; import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.Span; import io.github.rosemoe.sora.lang.styling.Styles; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; -import java.util.concurrent.CompletableFuture; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.Token; -import org.jetbrains.annotations.Contract; /** * Code analyzer for the Java Language * * @author Akash Yadav */ -public class JavaAnalyzer extends SimpleAnalyzeManager { +public class JavaAnalyzer extends SimpleAnalyzeManager implements IAnalyzeManager { private static final Logger LOG = Logger.instance("JavaAnalyzer"); private final List ideDiagnostics = new ArrayList<>(); - private final ILanguageServer languageServer; private List diagnostics = new ArrayList<>(); - public JavaAnalyzer(ILanguageServer languageServer) { - this.languageServer = languageServer; + @Override + public void updateDiagnostics(@NonNull List diagnostics) { + this.diagnostics = diagnostics; } @NonNull + @Override public List getDiagnostics() { final var result = new ArrayList<>(ideDiagnostics); if (diagnostics != null && !diagnostics.isEmpty()) { @@ -83,23 +84,6 @@ public class JavaAnalyzer extends SimpleAnalyzeManager { @Override protected Styles analyze(StringBuilder text, Delegate delegate) { - final var args = getExtraArguments(); - if (args.containsKey(IDEEditor.KEY_FILE)) { - final var file = new File(args.getString(IDEEditor.KEY_FILE)); - if (languageServer != null && file.exists()) { - CompletableFuture.runAsync( - () -> { - diagnostics = languageServer.analyze(file.toPath()); - if (languageServer.getClient() != null) { - languageServer - .getClient() - .publishDiagnostics( - new DiagnosticResult(file.toPath(), diagnostics)); - } - }); - } - } - final CodePointCharStream stream; final var styles = new Styles(); final var colors = new MappedSpans.Builder(); diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java index 0357f42ff..d659b0066 100755 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -54,7 +54,7 @@ public class JavaLanguage extends IDELanguage { public JavaLanguage() { final var server = getLanguageServer(); - this.analyzer = new JavaAnalyzer(server); + this.analyzer = new JavaAnalyzer(); this.completer = new CommonCompletionProvider(server); this.newlineHandlers = new NewlineHandler[1]; diff --git a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java index 900669f9e..2fdba17fa 100755 --- a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java @@ -20,6 +20,7 @@ package com.itsaky.androidide.lsp; import android.view.View; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -31,6 +32,7 @@ import com.itsaky.androidide.adapters.SearchListAdapter; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.interfaces.EditorActivityProvider; +import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.tasks.TaskExecutor; @@ -47,7 +49,7 @@ import com.itsaky.lsp.models.Location; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; import com.itsaky.toaster.Toaster; -import io.github.rosemoe.sora.text.Content; + import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -55,6 +57,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import io.github.rosemoe.sora.text.Content; + /** AndroidIDE specific implementation of the LanguageClient */ public class IDELanguageClientImpl implements ILanguageClient { @@ -142,6 +146,17 @@ public class IDELanguageClientImpl implements ILanguageClient { File file = result.getFile().toFile(); if (!file.exists() || !file.isFile()) return; + final var editorView = activity().getEditorForFile(file); + if (editorView != null) { + final var editor = editorView.getEditor(); + if (editor != null) { + final var editorLanguage = editor.getEditorLanguage(); + if (editorLanguage instanceof IDELanguage) { + ((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics()); + } + } + } + diagnostics.put(file, result.getDiagnostics()); activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java index 67b32422b..adfd501fb 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java @@ -38,8 +38,10 @@ import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.SizeUtils; import com.itsaky.androidide.adapters.CompletionListAdapter; @@ -57,9 +59,10 @@ import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.inflater.values.ValuesTableFactory; import com.itsaky.lsp.models.Range; -import io.github.rosemoe.sora.event.ContentChangeEvent; -import io.github.rosemoe.sora.lang.EmptyLanguage; -import io.github.rosemoe.sora.text.Content; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + import java.io.File; import java.io.StringReader; import java.nio.file.Files; @@ -67,8 +70,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; + +import io.github.rosemoe.sora.event.ContentChangeEvent; +import io.github.rosemoe.sora.lang.EmptyLanguage; +import io.github.rosemoe.sora.text.Content; /** * A view that handles opened code editors. @@ -231,6 +236,13 @@ public class CodeEditorView extends FrameLayout { configureEditorIfNeeded(); } + public void onEditorSelected() { + final var editor = getEditor(); + if (editor != null) { + editor.onEditorSelected(); + } + } + public void undo() { if (binding.editor.canUndo()) { binding.editor.undo(); diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java index c7aa3c413..3d6da6384 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java @@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat; import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.language.IAnalyzeManager; import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.lsp.IDELanguageClientImpl; import com.itsaky.androidide.managers.PreferenceManager; @@ -238,6 +239,21 @@ public class IDEEditor extends CodeEditor { final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0); documentHandler.onFileOpened(event); } + + // request diagnostics + if (getFile() != null && getEditorLanguage() instanceof IDELanguage) { + CompletableFuture.supplyAsync(() -> mLanguageServer.analyze(getFile().toPath())) + .whenComplete( + (diagnostics, throwable) -> { + final var lang = getEditorLanguage(); + final var analyzeManager = lang.getAnalyzeManager(); + if (analyzeManager instanceof IAnalyzeManager) { + ((IAnalyzeManager) analyzeManager) + .updateDiagnostics(diagnostics); + analyzeManager.rerun(); + } + }); + } } if (file != null) { @@ -604,6 +620,17 @@ public class IDEEditor extends CodeEditor { ensureWindowsDismissed(); } + public void onEditorSelected() { + if (getFile() == null) { + return; + } + + final var path = getFile().toPath(); + if (mLanguageServer != null) { + mLanguageServer.getDocumentHandler().onFileSelected(path); + } + } + /** * Tells the language client to perform the given code action item. * diff --git a/lsp/api/src/main/java/com/itsaky/lsp/api/IDocumentHandler.java b/lsp/api/src/main/java/com/itsaky/lsp/api/IDocumentHandler.java index c2456ffad..79e4071bc 100644 --- a/lsp/api/src/main/java/com/itsaky/lsp/api/IDocumentHandler.java +++ b/lsp/api/src/main/java/com/itsaky/lsp/api/IDocumentHandler.java @@ -17,10 +17,13 @@ package com.itsaky.lsp.api; +import androidx.annotation.NonNull; + import com.itsaky.lsp.models.DocumentChangeEvent; import com.itsaky.lsp.models.DocumentCloseEvent; import com.itsaky.lsp.models.DocumentOpenEvent; import com.itsaky.lsp.models.DocumentSaveEvent; + import java.nio.file.Path; /** @@ -71,4 +74,12 @@ public interface IDocumentHandler { * @param event The document close event data. */ void onFileClosed(DocumentCloseEvent event); + + /** + * Notify the document handler that the user has selected the given file for editing. Usually, + * the given file is visible to user. + * + * @param path The file that was selected. + */ + void onFileSelected(@NonNull Path path); } diff --git a/lsp/api/src/main/java/com/itsaky/lsp/util/NoDocumentHandler.java b/lsp/api/src/main/java/com/itsaky/lsp/util/NoDocumentHandler.java index b083d1faf..ba1e854cc 100644 --- a/lsp/api/src/main/java/com/itsaky/lsp/util/NoDocumentHandler.java +++ b/lsp/api/src/main/java/com/itsaky/lsp/util/NoDocumentHandler.java @@ -17,11 +17,14 @@ package com.itsaky.lsp.util; +import androidx.annotation.NonNull; + import com.itsaky.lsp.api.IDocumentHandler; import com.itsaky.lsp.models.DocumentChangeEvent; import com.itsaky.lsp.models.DocumentCloseEvent; import com.itsaky.lsp.models.DocumentOpenEvent; import com.itsaky.lsp.models.DocumentSaveEvent; + import java.nio.file.Path; /** @@ -45,4 +48,7 @@ public class NoDocumentHandler implements IDocumentHandler { @Override public void onFileClosed(DocumentCloseEvent event) {} + + @Override + public void onFileSelected(@NonNull Path path) {} } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/JavaLanguageServer.java b/lsp/java/src/main/java/com/itsaky/lsp/java/JavaLanguageServer.java index 9a6df117f..e74c94c0b 100644 --- a/lsp/java/src/main/java/com/itsaky/lsp/java/JavaLanguageServer.java +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/JavaLanguageServer.java @@ -19,6 +19,7 @@ package com.itsaky.lsp.java; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.api.ICompletionProvider; import com.itsaky.lsp.api.IDocumentHandler; @@ -36,11 +37,13 @@ import com.itsaky.lsp.java.providers.JavaDiagnosticProvider; import com.itsaky.lsp.java.providers.JavaSelectionProvider; import com.itsaky.lsp.java.providers.ReferenceProvider; import com.itsaky.lsp.java.providers.SignatureProvider; +import com.itsaky.lsp.java.utils.AnalyzeTimer; import com.itsaky.lsp.models.CodeActionParams; import com.itsaky.lsp.models.CodeActionResult; import com.itsaky.lsp.models.DefinitionParams; import com.itsaky.lsp.models.DefinitionResult; import com.itsaky.lsp.models.DiagnosticItem; +import com.itsaky.lsp.models.DiagnosticResult; import com.itsaky.lsp.models.DocumentChangeEvent; import com.itsaky.lsp.models.DocumentCloseEvent; import com.itsaky.lsp.models.DocumentOpenEvent; @@ -54,13 +57,16 @@ import com.itsaky.lsp.models.ServerCapabilities; import com.itsaky.lsp.models.SignatureHelp; import com.itsaky.lsp.models.SignatureHelpParams; import com.itsaky.lsp.util.NoCompletionsProvider; + import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Objects; public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { private static final Logger LOG = Logger.instance("JavaLanguageServer"); + private final AnalyzeTimer analyzeTimer; private ILanguageClient client; private IServerSettings settings; private JavaCompilerService compiler; @@ -68,11 +74,13 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { private boolean initialized; private boolean createCompiler; private ServerCapabilities capabilities; + private Path selectedFile; public JavaLanguageServer() { this.initialized = false; this.createCompiler = true; this.configuration = new JavaServerConfiguration(); + this.analyzeTimer = new AnalyzeTimer(this::analyzeSelected); applySettings(getSettings()); } @@ -87,6 +95,19 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { return compiler; } + private void analyzeSelected() { + if (this.selectedFile == null) { + return; + } + + final List diagnostics = analyze(selectedFile); + if (client != null) { + client.publishDiagnostics(new DiagnosticResult(this.selectedFile, diagnostics)); + } + + this.analyzeTimer.restart(); + } + public IServerSettings getSettings() { if (settings == null) { settings = JavaServerSettings.getInstance(); @@ -136,6 +157,7 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { } FileStore.shutdown(); + this.analyzeTimer.shutdown(); initialized = false; } @@ -265,7 +287,11 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { } @Override - public void onContentChange(DocumentChangeEvent event) { + public void onContentChange(@NonNull DocumentChangeEvent event) { + // If a file's content is changed, it is definitely visible to user. + onFileSelected(event.getChangedFile()); + + this.analyzeTimer.restart(); FileStore.change(event); } @@ -278,4 +304,13 @@ public class JavaLanguageServer implements ILanguageServer, IDocumentHandler { public void onFileClosed(DocumentCloseEvent event) { FileStore.close(event); } + + @Override + public void onFileSelected(@NonNull Path path) { + if (Objects.equals(this.selectedFile, path)) { + return; + } + + this.selectedFile = path; + } } diff --git a/lsp/java/src/main/java/com/itsaky/lsp/java/utils/AnalyzeTimer.java b/lsp/java/src/main/java/com/itsaky/lsp/java/utils/AnalyzeTimer.java new file mode 100644 index 000000000..6a912165d --- /dev/null +++ b/lsp/java/src/main/java/com/itsaky/lsp/java/utils/AnalyzeTimer.java @@ -0,0 +1,88 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.lsp.java.utils; + +import android.os.Handler; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * A timer which helps the language server to run a lint check after a specified interval. + * + * @author Akash Yadav + */ +public class AnalyzeTimer { + + public static final long DEFAULT_INTERVAL = 400; + @NonNull private final Handler timerHandler; + @NonNull private final Runnable timerCallback; + private long interval; + + /** + * Creates a new AnalyzeTimer instance. + * + * @param timerCallback The callback that will be invoked after the timer has ended. + */ + public AnalyzeTimer(@NonNull Runnable timerCallback) { + this.timerHandler = new Handler(); + this.interval = DEFAULT_INTERVAL; + this.timerCallback = timerCallback; + + Objects.requireNonNull(this.timerCallback, "Callback cannot be null"); + } + + /** + * Get the interval set to this timer. + * + * @return The interval. + */ + public long getInterval() { + return interval; + } + + /** + * Set the interval for the lint timer. + * + * @param interval The interval after which timerCallback will be called. + */ + public void setInterval(long interval) { + this.interval = interval; + + if (this.interval <= 0) { + throw new IllegalArgumentException("Invalid interval specified for timer."); + } + } + + /** Starts the timer. */ + public void start() { + restart(); + } + + /** Restarts the timer. */ + public void restart() { + timerHandler.removeCallbacks(timerCallback); + timerHandler.postDelayed(timerCallback, interval); + } + + /** Shutdown the timer. Cancels any running timers. */ + public void shutdown() { + timerHandler.removeCallbacks(timerCallback); + } +}