diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
index f067ea64a..60c240499 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/edits/AdvancedJavaEditHandler.kt
@@ -41,9 +41,7 @@ abstract class AdvancedJavaEditHandler(protected val file: Path) : BaseJavaEditH
index: Int
) {
val compiler = JavaCompilerProvider.get(ProjectManager.findModuleForFile(file) ?: return)
- if (compiler != null) {
- performEdits(compiler, editor, item)
- }
+ performEdits(compiler, editor, item)
executeCommand(editor, item.command)
}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
index a95984766..bdf870a0f 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
@@ -24,6 +24,7 @@ import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.edits.ClassImportEditHandler
import com.itsaky.androidide.lsp.java.models.JavaCompletionItem
import com.itsaky.androidide.lsp.java.providers.BaseJavaServiceProvider
+import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippet
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.lsp.models.ClassCompletionData
import com.itsaky.androidide.lsp.models.Command
@@ -42,6 +43,7 @@ import com.itsaky.androidide.lsp.models.ICompletionData
import com.itsaky.androidide.lsp.models.InsertTextFormat.SNIPPET
import com.itsaky.androidide.lsp.models.MatchLevel
import com.itsaky.androidide.lsp.models.MethodCompletionData
+import com.itsaky.androidide.preferences.utils.indentationString
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
import com.itsaky.androidide.utils.ILogger
import java.nio.file.Path
@@ -291,6 +293,23 @@ abstract class IJavaCompletionProvider(
}
}
+ protected open fun snippetItem(snippet: JavaSnippet, matchLevel: MatchLevel, partial: String, indent: Int) : CompletionItem {
+ return JavaCompletionItem().apply {
+ this.label = snippet.label
+ this.detail = snippet.description
+ this.completionKind = CompletionItemKind.SNIPPET
+ this.matchLevel = matchLevel
+ this.ideSortText = snippet.prefix
+ this.snippetDescription = describeSnippet(snippet.prefix)
+
+ val indentation = indentationString(indent)
+ this.insertTextFormat = SNIPPET
+ this.insertText = snippet.content.joinToString(separator = "\n") {
+ indentation + it.replace("\t", indentationString)
+ }
+ }
+ }
+
protected open fun kind(e: Element): CompletionItemKind {
abortIfCancelled()
abortCompletionIfCancelled()
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IdentifierCompletionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IdentifierCompletionProvider.kt
index 38127841a..ff1393fe6 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IdentifierCompletionProvider.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IdentifierCompletionProvider.kt
@@ -23,8 +23,8 @@ import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionResult
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
-import openjdk.source.util.TreePath
import java.nio.file.Path
+import openjdk.source.util.TreePath
/** @author Akash Yadav */
class IdentifierCompletionProvider(
@@ -44,6 +44,12 @@ class IdentifierCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
+
+ val snippets =
+ SnippetCompletionProvider(cursor, file, compiler, settings)
+ .complete(task, path, partial, endsWithParen)
+ list.addAll(snippets.items)
+
val scopeMembers =
ScopeCompletionProvider(file, cursor, compiler, settings)
.complete(task, path, partial, endsWithParen)
@@ -52,13 +58,7 @@ class IdentifierCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
val staticImports =
- StaticImportCompletionProvider(
- file,
- cursor,
- compiler,
- settings,
- path.compilationUnit
- )
+ StaticImportCompletionProvider(file, cursor, compiler, settings, path.compilationUnit)
.complete(task, path, partial, endsWithParen)
list.addAll(staticImports.items)
@@ -68,13 +68,7 @@ class IdentifierCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
val classNames =
- ClassNamesCompletionProvider(
- file,
- cursor,
- compiler,
- settings,
- path.compilationUnit
- )
+ ClassNamesCompletionProvider(file, cursor, compiler, settings, path.compilationUnit)
.complete(task, path, partial, endsWithParen)
list.addAll(classNames.items)
}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/SnippetCompletionProvider.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/SnippetCompletionProvider.kt
new file mode 100644
index 000000000..33b63bf1b
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/SnippetCompletionProvider.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.lsp.java.providers.completion
+
+import com.itsaky.androidide.lsp.api.IServerSettings
+import com.itsaky.androidide.lsp.java.compiler.CompileTask
+import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
+import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippet
+import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetRepository
+import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetScope
+import com.itsaky.androidide.lsp.java.utils.EditHelper
+import com.itsaky.androidide.lsp.models.CompletionItem
+import com.itsaky.androidide.lsp.models.CompletionResult
+import com.itsaky.androidide.lsp.models.MatchLevel
+import java.nio.file.Path
+import openjdk.source.tree.ClassTree
+import openjdk.source.tree.CompilationUnitTree
+import openjdk.source.tree.MethodTree
+import openjdk.source.util.TreePath
+
+/**
+ * Provides snippet completion for Java files.
+ *
+ * @author Akash Yadav
+ */
+class SnippetCompletionProvider(
+ cursor: Long,
+ completingFile: Path,
+ compiler: JavaCompilerService,
+ settings: IServerSettings
+) : IJavaCompletionProvider(cursor, completingFile, compiler, settings) {
+
+ override fun doComplete(
+ task: CompileTask,
+ path: TreePath,
+ partial: String,
+ endsWithParen: Boolean
+ ): CompletionResult {
+ val scope = findSnippetScope(path) ?: return CompletionResult.EMPTY
+ return when (scope.leaf) {
+ is CompilationUnitTree -> completeTopLevelSnippets(task, path, partial)
+ is ClassTree -> completeMemberSnippets(task, path, partial)
+ is MethodTree -> completeLocalSnippets(task, path, partial)
+ else -> CompletionResult.EMPTY
+ }
+ }
+
+ private fun completeTopLevelSnippets(
+ task: CompileTask,
+ path: TreePath,
+ partial: String
+ ): CompletionResult {
+ return CompletionResult.EMPTY
+ }
+
+ private fun completeMemberSnippets(
+ task: CompileTask,
+ path: TreePath,
+ partial: String
+ ): CompletionResult {
+ return CompletionResult.EMPTY
+ }
+
+ private fun completeLocalSnippets(
+ task: CompileTask,
+ path: TreePath,
+ partial: String
+ ): CompletionResult {
+ val items = mutableListOf()
+ val snippets =
+ mutableListOf().apply {
+ JavaSnippetRepository.snippets[JavaSnippetScope.LOCAL]?.let { addAll(it) }
+ JavaSnippetRepository.snippets[JavaSnippetScope.GLOBAL]?.let { addAll(it) }
+ }
+
+ val indent = EditHelper.indent(task.task, task.root(), path.leaf)
+ for (snippet in snippets) {
+ val matchLevel = matchLevel(snippet.prefix, partial)
+ if (matchLevel == MatchLevel.NO_MATCH) {
+ continue
+ }
+
+ items.add(snippetItem(snippet, matchLevel, partial, indent))
+ }
+
+ return CompletionResult(items)
+ }
+
+ private fun findSnippetScope(path: TreePath?): TreePath? {
+ var treePath = path
+ while (treePath != null) {
+ if (treePath.leaf.let { it is CompilationUnitTree || it is ClassTree || it is MethodTree }) {
+ return treePath
+ }
+ treePath = treePath.parentPath
+ }
+ return null
+ }
+}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippet.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippet.kt
new file mode 100644
index 000000000..71e264749
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippet.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.lsp.java.providers.snippet
+
+/**
+ * Java snippet.
+ *
+ * @author Akash Yadav
+ */
+data class JavaSnippet(
+ val prefix: String,
+ val label: String,
+ val description: String,
+ val content: Array
+) {
+
+ constructor(
+ prefix: String,
+ label: String,
+ description: String,
+ content: () -> Array
+ ) : this(prefix, label, description, content())
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is JavaSnippet) return false
+
+ if (label != other.label) return false
+ if (description != other.description) return false
+ if (prefix != other.prefix) return false
+ if (!content.contentEquals(other.content)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = label.hashCode()
+ result = 31 * result + description.hashCode()
+ result = 31 * result + prefix.hashCode()
+ result = 31 * result + content.contentHashCode()
+ return result
+ }
+}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetRepository.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetRepository.kt
new file mode 100644
index 000000000..2cf7ea7e0
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.lsp.java.providers.snippet
+
+/**
+ * Repository to store various snippets for Java.
+ *
+ * @author Akash Yadav
+ */
+object JavaSnippetRepository {
+
+ val snippets: Map> by lazy { createSnippets() }
+
+ private fun createSnippets(): Map> {
+ return mutableMapOf>().apply {
+ this[JavaSnippetScope.GLOBAL] = createGlobalSnippets()
+ this[JavaSnippetScope.TOP_LEVEL] = createTopLevelSnippets()
+ this[JavaSnippetScope.MEMBER] = createMemberSnippets()
+ this[JavaSnippetScope.LOCAL] = createLocalSnippets()
+ }
+ }
+
+ private fun createGlobalSnippets(): List {
+ return listOf()
+ }
+
+ private fun createTopLevelSnippets(): List {
+ return listOf()
+ }
+
+ private fun createMemberSnippets(): List {
+ return listOf()
+ }
+
+ private fun createLocalSnippets(): List {
+ return listOf(
+ JavaSnippet("for", "Indexed for loop", "0 -> count") {
+ arrayOf("for (int \${1:i} = 0; \$1 < \${2:count}; ++\$1) {", "\t\$0", "}")
+ },
+ JavaSnippet("forr", "Reverse-indexed for loop", "count-1 -> 0") {
+ arrayOf("for (int \${1:i} = \${2:count} - 1; \$1 >= 0; --\$1) {", "\t\$0", "}")
+ }
+ )
+ }
+}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetScope.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetScope.kt
new file mode 100644
index 000000000..e9bf1850c
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/providers/snippet/JavaSnippetScope.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.lsp.java.providers.snippet
+
+/**
+ * Scope for [JavaSnippet].
+ *
+ * @author Akash Yadav
+ */
+enum class JavaSnippetScope {
+
+ /**
+ * Snippets that can be used at the top level. This includes snippes such as class, interface,
+ * enum templates.
+ */
+ TOP_LEVEL,
+
+ /** Snippets that can be used at the member level i.e. inside a class tree. */
+ MEMBER,
+
+ /** Snippets that can be used at a local level. E.g. inside a method or constructor. */
+ LOCAL,
+
+ /** Snippets that can be used anywhere in the code, irrespective of the current scope. */
+ GLOBAL
+}