mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat(lsp/java): add basic snippet support for Java
This commit is contained in:
+1
-3
@@ -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)
|
||||
}
|
||||
|
||||
+19
@@ -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()
|
||||
|
||||
+9
-15
@@ -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)
|
||||
}
|
||||
|
||||
+114
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<CompletionItem>()
|
||||
val snippets =
|
||||
mutableListOf<JavaSnippet>().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
|
||||
}
|
||||
}
|
||||
+58
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String>
|
||||
) {
|
||||
|
||||
constructor(
|
||||
prefix: String,
|
||||
label: String,
|
||||
description: String,
|
||||
content: () -> Array<String>
|
||||
) : 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
|
||||
}
|
||||
}
|
||||
+60
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.providers.snippet
|
||||
|
||||
/**
|
||||
* Repository to store various snippets for Java.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object JavaSnippetRepository {
|
||||
|
||||
val snippets: Map<JavaSnippetScope, List<JavaSnippet>> by lazy { createSnippets() }
|
||||
|
||||
private fun createSnippets(): Map<JavaSnippetScope, List<JavaSnippet>> {
|
||||
return mutableMapOf<JavaSnippetScope, List<JavaSnippet>>().apply {
|
||||
this[JavaSnippetScope.GLOBAL] = createGlobalSnippets()
|
||||
this[JavaSnippetScope.TOP_LEVEL] = createTopLevelSnippets()
|
||||
this[JavaSnippetScope.MEMBER] = createMemberSnippets()
|
||||
this[JavaSnippetScope.LOCAL] = createLocalSnippets()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createGlobalSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createTopLevelSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createMemberSnippets(): List<JavaSnippet> {
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun createLocalSnippets(): List<JavaSnippet> {
|
||||
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", "}")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+41
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user